I have the below drawing, made of random shapes with various number of points, to which I can add, through the following XSLT, textboxes.
The solution proposed in this thread (i.e. x="50%" y ="50%"
and dominant-baseline="middle"
text-anchor="middle"
) does not work, as all such textboxes end up in the same position of the drawing, overlapping. I would actually like them to be in the center of each path they are named after.
<text x="" y="" id="{$id}-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000 " dominant-baseline="middle" text-anchor="middle">
<tspan id="{$id}-tspan" x="" y="">
<xsl:value-of select="$id"/>
</tspan>
</text>
SVG
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="exportSvg" width="400" height="400">
<defs/>
<rect width="400" height="400" transform="translate(0, 0)" fill="rgb(255, 255, 255)" style="fill:rgb(255, 255, 255);"/>
<g>
<g id="Drawing-svg" clip-path="url(#rect-mask-Drawing)">
<clipPath id="rect-mask-Drawing">
<rect x="0" y="0" width="400" height="400"/>
</clipPath>
<g id="chart-svg">
<g id="svg-main" clip-path="url(#rect-mask-Main)">
<clipPath id="rect-mask-Main">
<rect x="0" y="0" width="400" height="400"/>
</clipPath>
<g id="Drawing-svg">
<g id="Parts-svg">
<g id="Section-svg">
<g id="Item1-svg">
<path d="M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z" stroke="#000000" style="fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id="Item1"/>
</g>
<g id="Item2-svg">
<path d="M 198.06872,89.614437 -9.21291,31.643703 -23.42303,34.67823 51.52002,20.68699 47.20879,-57.62707 z" stroke="#000000" style="fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id="Item2"/>
</g>
<g id="Item3-svg">
<path d="M 161.0455,182.56778 -41.68122,-5.64443 15.98375,27.05111 67.62172,3.73783 32.80201,-13.55927 z" stroke="#000000" style="fill:#e6e6e6;stroke-width:0.3;stroke-linecap:round;stroke-linejoin:round" id="Item3"/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
XSLT
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
exclude-result-prefixes="svg"
version="1.0">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="svg:g[@id[starts-with(., 'Item')]]">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
<xsl:variable name="id" select="substring-before(@id, '-')"/>
<text x="" y="" id="{$id}-text" style="-inkscape-font-specification:'Calibri, Normal';font-family:Calibri;font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000 " dominant-baseline="middle" text-anchor="middle">
<tspan id="{$id}-tspan" x="" y="">
<xsl:value-of select="$id"/>
</tspan>
</text>
</xsl:copy>
</xsl:template>
<xsl:template match="processing-instruction('xml-stylesheet')"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
CodePudding user response:
I made an attempt, below.
This is actually quite fiddly to do in XSLT 1, because parsing the svg:path/@d
value requires a lot of string processing and recursion.
I wrote a named template get-bounding-box-edge-value
which you call with 2 parameters; a string containing a list of x,y coordinates, and a string specifying which edge you want to find (either 'TOP', 'BOTTOM', 'LEFT', or 'RIGHT'). The template calls itself recursively to process the list, and returns the minimum y coordinate for 'TOP', the maximum y for 'BOTTOM', the minimum x for 'LEFT', and the maximum x for 'RIGHT'.
Then when processing an svg:path
, I call this template 4 times to retrieve the 4 edges that define the bounding box of the path, calculate the centre point from those 4 values, and position the svg:text
element at that point, setting the dominant-baseline
and text-anchor
attributes so that the textual content of the svg:text
is centred around that point.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" exclude-result-prefixes="svg" version="1.0">
<xsl:output method="xml" encoding="utf-8" omit-xml-declaration="yes" indent="yes"/>
<xsl:template name="get-bounding-box-edge-value">
<xsl:param name="which-edge"/>
<xsl:param name="coordinate-path"/>
<xsl:variable name="next-coordinate-pair" select="substring-before($coordinate-path, ' ')"/>
<xsl:variable name="remaining-coordinates" select="substring-after($coordinate-path, ' ')"/>
<!-- get the next value (either an x or y coordinate) from the first of the list of coordinate pairs -->
<xsl:variable name="next-value">
<xsl:choose>
<xsl:when test="$which-edge='TOP' or $which-edge='BOTTOM'">
<!-- we want the Y coordinate -->
<xsl:value-of select="substring-after($next-coordinate-pair, ',')"/>
</xsl:when>
<xsl:otherwise>
<!-- we want the X coordinate -->
<xsl:value-of select="substring-before($next-coordinate-pair, ',')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$remaining-coordinates">
<xsl:variable name="remaining-edge-value">
<xsl:call-template name="get-bounding-box-edge-value">
<xsl:with-param name="which-edge" select="$which-edge"/>
<xsl:with-param name="coordinate-path" select="$remaining-coordinates"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$which-edge='TOP' or $which-edge='LEFT'">
<!-- we're calculating the minimum value (NB 0,0 is the upper-left corner) -->
<xsl:choose>
<xsl:when test="number($next-value) < number($remaining-edge-value)">
<xsl:value-of select="$next-value"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$remaining-edge-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- we're calculating the maximum value -->
<xsl:choose>
<xsl:when test="number($next-value) > number($remaining-edge-value)">
<xsl:value-of select="$next-value"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$remaining-edge-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<!-- there are no more coordinates in the path - this is the last coordinate pair -->
<!-- so we just return the value taken from this coordinate pair -->
<xsl:value-of select="$next-value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="svg:g[starts-with(@id, 'Item')]">
<!-- calculate the bounding box of the path by extracting the TOP, LEFT, RIGHT, and BOTTOM coordinate value-->
<xsl:variable name="coordinate-path" select="translate(substring-after(svg:path/@d, 'M '), 'zZ', '')"/>
<xsl:variable name="top">
<xsl:call-template name="get-bounding-box-edge-value">
<xsl:with-param name="which-edge">TOP</xsl:with-param>
<xsl:with-param name="coordinate-path" select="$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="left">
<xsl:call-template name="get-bounding-box-edge-value">
<xsl:with-param name="which-edge">LEFT</xsl:with-param>
<xsl:with-param name="coordinate-path" select="$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="bottom">
<xsl:call-template name="get-bounding-box-edge-value">
<xsl:with-param name="which-edge">BOTTOM</xsl:with-param>
<xsl:with-param name="coordinate-path" select="$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="right">
<xsl:call-template name="get-bounding-box-edge-value">
<xsl:with-param name="which-edge">RIGHT</xsl:with-param>
<xsl:with-param name="coordinate-path" select="$coordinate-path"/>
</xsl:call-template>
</xsl:variable>
<!-- calculate the coordinates of the centroid -->
<xsl:variable name="center-x" select="(number($left) number($right)) div 2"/>
<xsl:variable name="center-y" select="(number($top) number($bottom)) div 2"/>
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
<xsl:variable name="id" select="substring-before(@id, '-')"/>
<text x="50%" y="50%" id="{$id}-text" style="
-inkscape-font-specification:'Calibri, Normal';
font-family:Calibri;font-weight:normal;font-style:normal;
font-stretch:normal;font-variant:normal;font-size:20px;font-variant-ligatures:normal;
font-variant-caps:normal;font-variant-numeric:normal;
font-variant-east-asian:normal;
fill:#000000;
text-align:center
">
<tspan id="{$id}-tspan" x="{$center-x}" y="{$center-y}" dominant-baseline="middle" text-anchor="middle">
<xsl:value-of select="$id"/>
</tspan>
</text>
</xsl:copy>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
CodePudding user response:
The following templates transform a path like
<path d="M 155.09357,45.542471 104.77897,86.931934 75,200 152.79121,141.87343 200,84.246354 Z"/>
into a specification of its center like
<center x="137.5" y="122.7712355"/>
They make certain assumptions about the commas and spaces in the path, but these can easily be adapted. They also use the non-standard function node-set
, which is supported by almost all XSLT 1.0 processors, just under different names, for example exslt:node-set
.
<xsl:template match="path">
<xsl:copy-of select="."/> <!-- copy the <path> element -->
<xsl:call-template name="bbox">
<xsl:with-param name="path" select="substring-before(substring-after(@d,'M '),' Z')"/>
<xsl:with-param name="bbox">
<bbox x="10000" X="-10000" y="10000" Y="-10000"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="bbox">
<xsl:param name="path"/>
<xsl:param name="bbox"/>
<xsl:variable name="b" select="node-set($bbox)/*"/>
<xsl:choose>
<xsl:when test="$path">
<xsl:variable name="x" select="number(substring-before($path,','))"/>
<xsl:variable name="y" select="number(substring-after(substring-before($path,' '),','))"/>
<xsl:variable name="next" select="substring-after($path,' ')"/>
<xsl:call-template name="bbox">
<xsl:with-param name="path" select="$next"/>
<xsl:with-param name="bbox">
<bbox>
<xsl:attribute name="x">
<xsl:choose>
<xsl:when test="$x < $b/@x">
<xsl:value-of select="$x"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$b/@x"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="X">
<xsl:choose>
<xsl:when test="$x > $b/@X">
<xsl:value-of select="$x"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$b/@X"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="y">
<xsl:choose>
<xsl:when test="$y < $b/@y">
<xsl:value-of select="$y"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$b/@y"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="Y">
<xsl:choose>
<xsl:when test="$y > $b/@Y">
<xsl:value-of select="$y"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$b/@Y"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</bbox>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<center x="{($b/@x $b/@X) div 2}" y="{($b/@y $b/@Y) div 2}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
For your particular case, replace the <center>
element with the text you want at these coordinates:
<xsl:variable name="id" select="substring-before(parent::g/@id,'-')"/>
<text x="{($b/@x $b/@X) div 2}" y="{($b/@y $b/@Y) div 2}" id="{$id}-text">
<tspan id="{$id}-tspan" x="{($b/@x $b/@X) div 2}" y="{($b/@y $b/@Y) div 2}">
<xsl:value-of select="$id"/>
</tspan>
</text>