mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Export parametric SVG parameters, will fallback symbols for the system that cannot understand them
This commit is contained in:
parent
5984b21852
commit
701d4440ac
@ -501,4 +501,20 @@ class QgsSymbolLayerUtils
|
||||
*/
|
||||
static void mergeScaleDependencies( int mScaleMinDenom, int mScaleMaxDenom, QgsStringMap& props );
|
||||
|
||||
/**
|
||||
* Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL parameters,
|
||||
* a fallback SVG without parameters, and a final fallback as a mark with the right colors and outline for systems
|
||||
* that cannot do SVG at all
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static void parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem,
|
||||
const QString& path,
|
||||
const QColor& fillColor, double size, const QColor& outlineColor, double outlineWidth );
|
||||
|
||||
/**
|
||||
* Encodes a reference to a parametric SVG into a path with parameters according to the SVG Parameters spec
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static QString getSvgParametricPath( const QString& basePath, const QColor& fillColor, const QColor& borderColor, double borderWidth );
|
||||
|
||||
};
|
||||
|
@ -2083,8 +2083,10 @@ void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, cons
|
||||
|
||||
if ( !mSvgFilePath.isEmpty() )
|
||||
{
|
||||
double partternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
|
||||
QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, mSvgFilePath, QStringLiteral( "image/svg+xml" ), mColor, partternWidth );
|
||||
// encode a parametric SVG reference
|
||||
double patternWidth = QgsSymbolLayerUtils::rescaleUom( mPatternWidth, mPatternWidthUnit, props );
|
||||
double outlineWidth = QgsSymbolLayerUtils::rescaleUom( mSvgOutlineWidth, mSvgOutlineWidthUnit, props );
|
||||
QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mSvgFilePath, mColor, patternWidth, mSvgOutlineColor, outlineWidth );
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2093,12 +2095,6 @@ void QgsSVGFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &element, cons
|
||||
symbolizerElem.appendChild( doc.createComment( QStringLiteral( "SVG from data not implemented yet" ) ) );
|
||||
}
|
||||
|
||||
if ( mSvgOutlineColor.isValid() || mSvgOutlineWidth >= 0 )
|
||||
{
|
||||
double svgOutlineWidth = QgsSymbolLayerUtils::rescaleUom( mSvgOutlineWidth, mSvgOutlineWidthUnit, props );
|
||||
QgsSymbolLayerUtils::lineToSld( doc, graphicElem, Qt::SolidLine, mSvgOutlineColor, svgOutlineWidth );
|
||||
}
|
||||
|
||||
// <Rotation>
|
||||
QString angleFunc;
|
||||
bool ok;
|
||||
|
@ -2194,8 +2194,10 @@ void QgsSvgMarkerSymbolLayer::writeSldMarker( QDomDocument &doc, QDomElement &el
|
||||
QDomElement graphicElem = doc.createElement( QStringLiteral( "se:Graphic" ) );
|
||||
element.appendChild( graphicElem );
|
||||
|
||||
// encode a parametric SVG reference
|
||||
double size = QgsSymbolLayerUtils::rescaleUom( mSize, mSizeUnit, props );
|
||||
QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, mPath, QStringLiteral( "image/svg+xml" ), mColor, size );
|
||||
double outlineWidth = QgsSymbolLayerUtils::rescaleUom( mOutlineWidth, mOutlineWidthUnit, props );
|
||||
QgsSymbolLayerUtils::parametricSvgToSld( doc, graphicElem, mPath, mColor, size, mOutlineColor, outlineWidth );
|
||||
|
||||
// <Rotation>
|
||||
QString angleFunc;
|
||||
|
@ -68,7 +68,9 @@ QColor QgsSymbolLayerUtils::decodeColor( const QString& str )
|
||||
|
||||
QString QgsSymbolLayerUtils::encodeSldAlpha( int alpha )
|
||||
{
|
||||
return QString::number( alpha / 255.0, 'f', 2 );
|
||||
QString result;
|
||||
result.sprintf( "%.2g", alpha / 255.0 );
|
||||
return result;
|
||||
}
|
||||
|
||||
int QgsSymbolLayerUtils::decodeSldAlpha( const QString& str )
|
||||
@ -1953,6 +1955,69 @@ void QgsSymbolLayerUtils::externalGraphicToSld( QDomDocument &doc, QDomElement &
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSymbolLayerUtils::parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem,
|
||||
const QString& path, const QColor& fillColor, double size, const QColor& outlineColor, double outlineWidth )
|
||||
{
|
||||
// Parametric SVG paths are an extension that few systems will understand, but se:Graphic allows for fallback
|
||||
// symbols, this encodes the full parametric path first, the pure shape second, and a mark with the right colors as
|
||||
// a last resort for systems that cannot do SVG at all
|
||||
|
||||
// encode parametric version with all coloring details (size is going to be encoded by the last fallback)
|
||||
graphicElem.appendChild( doc.createComment( QStringLiteral( "Parametric SVG" ) ) );
|
||||
QString parametricPath = getSvgParametricPath( path, fillColor, outlineColor, outlineWidth );
|
||||
QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, parametricPath, QStringLiteral( "image/svg+xml" ), fillColor, -1 );
|
||||
// also encode a fallback version without parameters, in case a renderer gets confused by the parameters
|
||||
graphicElem.appendChild( doc.createComment( QStringLiteral( "Plain SVG fallback, no parameters" ) ) );
|
||||
QgsSymbolLayerUtils::externalGraphicToSld( doc, graphicElem, path, QStringLiteral( "image/svg+xml" ), fillColor, -1 );
|
||||
// finally encode a simple mark with the right colors/outlines for renderers that cannot do SVG at all
|
||||
graphicElem.appendChild( doc.createComment( QStringLiteral( "Well known marker fallback" ) ) );
|
||||
QgsSymbolLayerUtils::wellKnownMarkerToSld( doc, graphicElem, QStringLiteral( "square" ), fillColor, outlineColor, Qt::PenStyle::SolidLine, outlineWidth, -1 );
|
||||
|
||||
// size is encoded here, it's part of se:Graphic, not attached to the single symbol
|
||||
if ( size >= 0 )
|
||||
{
|
||||
QDomElement sizeElem = doc.createElement( QStringLiteral( "se:Size" ) );
|
||||
sizeElem.appendChild( doc.createTextNode( qgsDoubleToString( size ) ) );
|
||||
graphicElem.appendChild( sizeElem );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString QgsSymbolLayerUtils::getSvgParametricPath( const QString& basePath, const QColor& fillColor, const QColor& borderColor, double borderWidth )
|
||||
{
|
||||
QUrl url = QUrl();
|
||||
if ( fillColor.isValid() )
|
||||
{
|
||||
url.addQueryItem( QStringLiteral( "fill" ), fillColor.name() );
|
||||
url.addQueryItem( QStringLiteral( "fill-opacity" ), encodeSldAlpha( fillColor.alpha() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
url.addQueryItem( "fill", QStringLiteral( "#000000" ) );
|
||||
url.addQueryItem( "fill-opacity", QStringLiteral( "1" ) );
|
||||
}
|
||||
if ( borderColor.isValid() )
|
||||
{
|
||||
url.addQueryItem( QStringLiteral( "outline" ), borderColor.name() );
|
||||
url.addQueryItem( QStringLiteral( "outline-opacity" ), encodeSldAlpha( borderColor.alpha() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
url.addQueryItem( QStringLiteral( "outline" ), QStringLiteral( "#000000" ) );
|
||||
url.addQueryItem( QStringLiteral( "outline-opacity" ), QStringLiteral( "1" ) );
|
||||
}
|
||||
url.addQueryItem( QStringLiteral( "outline-width" ), QString::number( borderWidth ) );
|
||||
QString params = url.encodedQuery();
|
||||
if ( params.isEmpty() )
|
||||
{
|
||||
return basePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return basePath + "?" + params;
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsSymbolLayerUtils::externalGraphicFromSld( QDomElement &element,
|
||||
QString &path, QString &mime,
|
||||
QColor &color, double &size )
|
||||
|
@ -587,6 +587,22 @@ class CORE_EXPORT QgsSymbolLayerUtils
|
||||
*/
|
||||
static void mergeScaleDependencies( int mScaleMinDenom, int mScaleMaxDenom, QgsStringMap& props );
|
||||
|
||||
/**
|
||||
* Encodes a reference to a parametric SVG into SLD, as a succession of parametric SVG using URL parameters,
|
||||
* a fallback SVG without parameters, and a final fallback as a mark with the right colors and outline for systems
|
||||
* that cannot do SVG at all
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static void parametricSvgToSld( QDomDocument &doc, QDomElement &graphicElem,
|
||||
const QString& path,
|
||||
const QColor& fillColor, double size, const QColor& outlineColor, double outlineWidth );
|
||||
|
||||
/**
|
||||
* Encodes a reference to a parametric SVG into a path with parameters according to the SVG Parameters spec
|
||||
* @note added in 3.0
|
||||
*/
|
||||
static QString getSvgParametricPath( const QString& basePath, const QColor& fillColor, const QColor& borderColor, double borderWidth );
|
||||
|
||||
};
|
||||
|
||||
class QPolygonF;
|
||||
|
@ -60,9 +60,9 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
|
||||
|
||||
self.assertStaticRotation(root, '50')
|
||||
|
||||
def assertStaticRotation(self, root, expectedValue):
|
||||
def assertStaticRotation(self, root, expectedValue, index=0):
|
||||
# Check the rotation element is a literal, not a
|
||||
rotation = root.elementsByTagName('se:Rotation').item(0)
|
||||
rotation = root.elementsByTagName('se:Rotation').item(index)
|
||||
literal = rotation.firstChild()
|
||||
self.assertEqual("ogc:Literal", literal.nodeName())
|
||||
self.assertEqual(expectedValue, literal.firstChild().nodeValue())
|
||||
@ -127,11 +127,21 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
|
||||
|
||||
def testSvgMarkerUnitDefault(self):
|
||||
symbol = QgsSvgMarkerSymbolLayer('symbols/star.svg', 10, 90)
|
||||
symbol.setFillColor(QColor("blue"))
|
||||
symbol.setOutlineWidth(1)
|
||||
symbol.setOutlineColor(QColor('red'))
|
||||
symbol.setPath('symbols/star.svg')
|
||||
symbol.setOffset(QPointF(5, 10))
|
||||
|
||||
dom, root = self.symbolToSld(symbol)
|
||||
# print("Svg marker mm: " + dom.toString())
|
||||
|
||||
self.assertExternalGraphic(root, 0,
|
||||
'symbols/star.svg?fill=%230000ff&fill-opacity=1&outline=%23ff0000&outline-opacity=1&outline-width=4', 'image/svg+xml')
|
||||
self.assertExternalGraphic(root, 1,
|
||||
'symbols/star.svg', 'image/svg+xml')
|
||||
self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#ff0000', 4)
|
||||
|
||||
# Check the size has been rescaled
|
||||
self.assertStaticSize(root, '36')
|
||||
|
||||
@ -141,11 +151,21 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
|
||||
|
||||
def testSvgMarkerUnitPixels(self):
|
||||
symbol = QgsSvgMarkerSymbolLayer('symbols/star.svg', 10, 0)
|
||||
symbol.setFillColor(QColor("blue"))
|
||||
symbol.setOutlineWidth(1)
|
||||
symbol.setOutlineColor(QColor('red'))
|
||||
symbol.setPath('symbols/star.svg')
|
||||
symbol.setOffset(QPointF(5, 10))
|
||||
symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
|
||||
dom, root = self.symbolToSld(symbol)
|
||||
# print("Svg marker unit px: " + dom.toString())
|
||||
|
||||
self.assertExternalGraphic(root, 0,
|
||||
'symbols/star.svg?fill=%230000ff&fill-opacity=1&outline=%23ff0000&outline-opacity=1&outline-width=1', 'image/svg+xml')
|
||||
self.assertExternalGraphic(root, 1,
|
||||
'symbols/star.svg', 'image/svg+xml')
|
||||
self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#ff0000', 1)
|
||||
|
||||
# Check the size has not been rescaled
|
||||
self.assertStaticSize(root, '10')
|
||||
self.assertStaticDisplacement(root, 5, 10)
|
||||
@ -154,7 +174,7 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
|
||||
symbol = QgsFontMarkerSymbolLayer('sans', ',', 10, QColor('black'), 45)
|
||||
symbol.setOffset(QPointF(5, 10))
|
||||
dom, root = self.symbolToSld(symbol)
|
||||
# print "Font marker unit mm: " + dom.toString()
|
||||
# print("Font marker unit mm: " + dom.toString())
|
||||
|
||||
# Check the size has been rescaled
|
||||
self.assertStaticSize(root, '36')
|
||||
@ -300,32 +320,47 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
|
||||
|
||||
def testSvgFillDefault(self):
|
||||
symbol = QgsSVGFillSymbolLayer('test/star.svg', 10, 45)
|
||||
symbol.setSvgFillColor(QColor('blue'))
|
||||
symbol.setSvgOutlineWidth(3)
|
||||
symbol.setSvgOutlineColor(QColor('yellow'))
|
||||
symbol.subSymbol().setWidth(10)
|
||||
|
||||
dom, root = self.symbolToSld(symbol)
|
||||
# print ("Svg fill mm: \n" + dom.toString())
|
||||
|
||||
self.assertExternalGraphic(root, 0,
|
||||
'test/star.svg?fill=%230000ff&fill-opacity=1&outline=%23ffff00&outline-opacity=1&outline-width=11', 'image/svg+xml')
|
||||
self.assertExternalGraphic(root, 1,
|
||||
'test/star.svg', 'image/svg+xml')
|
||||
self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#ffff00', 11)
|
||||
|
||||
self.assertStaticRotation(root, '45')
|
||||
self.assertStaticSize(root, '36')
|
||||
# width of the svg outline
|
||||
self.assertStrokeWidth(root, 1, 11)
|
||||
# width of the polygon outline
|
||||
self.assertStrokeWidth(root, 3, 1)
|
||||
lineSymbolizer = root.elementsByTagName('se:LineSymbolizer').item(0).toElement()
|
||||
self.assertStrokeWidth(lineSymbolizer, 1, 36)
|
||||
|
||||
def testSvgFillPixel(self):
|
||||
symbol = QgsSVGFillSymbolLayer('test/star.svg', 10, 45)
|
||||
symbol.setSvgFillColor(QColor('blue'))
|
||||
symbol.setSvgOutlineWidth(3)
|
||||
symbol.setOutputUnit(QgsUnitTypes.RenderPixels)
|
||||
symbol.subSymbol().setWidth(10)
|
||||
|
||||
dom, root = self.symbolToSld(symbol)
|
||||
# print ("Svg fill px: \n" + dom.toString())
|
||||
|
||||
self.assertExternalGraphic(root, 0,
|
||||
'test/star.svg?fill=%230000ff&fill-opacity=1&outline=%23000000&outline-opacity=1&outline-width=3', 'image/svg+xml')
|
||||
self.assertExternalGraphic(root, 1,
|
||||
'test/star.svg', 'image/svg+xml')
|
||||
self.assertWellKnownMark(root, 0, 'square', '#0000ff', '#000000', 3)
|
||||
|
||||
self.assertStaticRotation(root, '45')
|
||||
self.assertStaticSize(root, '10')
|
||||
# width of the svg outline
|
||||
self.assertStrokeWidth(root, 1, 3)
|
||||
# width of the polygon outline
|
||||
self.assertStrokeWidth(root, 3, 0.26)
|
||||
lineSymbolizer = root.elementsByTagName('se:LineSymbolizer').item(0).toElement()
|
||||
self.assertStrokeWidth(lineSymbolizer, 1, 10)
|
||||
|
||||
def testLineFillDefault(self):
|
||||
symbol = QgsLinePatternFillSymbolLayer()
|
||||
@ -497,10 +532,41 @@ class TestQgsSymbolLayerCreateSld(unittest.TestCase):
|
||||
size = root.elementsByTagName('se:Size').item(0)
|
||||
self.assertEqual(expectedValue, size.firstChild().nodeValue())
|
||||
|
||||
def assertExternalGraphic(self, root, index, expectedLink, expectedFormat):
|
||||
graphic = root.elementsByTagName('se:ExternalGraphic').item(index)
|
||||
onlineResource = graphic.firstChildElement('se:OnlineResource')
|
||||
self.assertEqual(expectedLink, onlineResource.attribute('xlink:href'))
|
||||
format = graphic.firstChildElement('se:Format')
|
||||
self.assertEqual(expectedFormat, format.firstChild().nodeValue())
|
||||
|
||||
def assertStaticPerpendicularOffset(self, root, expectedValue):
|
||||
offset = root.elementsByTagName('se:PerpendicularOffset').item(0)
|
||||
self.assertEqual(expectedValue, offset.firstChild().nodeValue())
|
||||
|
||||
def assertWellKnownMark(self, root, index, expectedName, expectedFill, expectedStroke, expectedStrokeWidth):
|
||||
mark = root.elementsByTagName('se:Mark').item(index)
|
||||
wkn = mark.firstChildElement('se:WellKnownName')
|
||||
self.assertEqual(expectedName, wkn.text())
|
||||
|
||||
fill = mark.firstChildElement('se:Fill')
|
||||
if expectedFill is None:
|
||||
self.assertTrue(fill.isNull())
|
||||
else:
|
||||
parameter = fill.firstChildElement('se:SvgParameter')
|
||||
self.assertEqual('fill', parameter.attribute('name'))
|
||||
self.assertEqual(expectedFill, parameter.text())
|
||||
|
||||
stroke = mark.firstChildElement('se:Stroke')
|
||||
if expectedStroke is None:
|
||||
self.assertTrue(stroke.isNull())
|
||||
else:
|
||||
parameter = stroke.firstChildElement('se:SvgParameter')
|
||||
self.assertEqual('stroke', parameter.attribute('name'))
|
||||
self.assertEqual(expectedStroke, parameter.text())
|
||||
parameter = parameter.nextSiblingElement('se:SvgParameter')
|
||||
self.assertEqual('stroke-width', parameter.attribute('name'))
|
||||
self.assertEqual(str(expectedStrokeWidth), parameter.text())
|
||||
|
||||
def symbolToSld(self, symbolLayer):
|
||||
dom = QDomDocument()
|
||||
root = dom.createElement("FakeRoot")
|
||||
|
Loading…
x
Reference in New Issue
Block a user