5

I am wondering if there is a way to curve text inserted as text boxes in QGIS print layout. I am familiar with how to curve layer labels. But in this case, I want to curve text that is not tied to any layer, and only exists on the layout.

1

2 Answers 2

7

It's not currently an option in QGIS 3.34, as @Xeppit pointed out.

If you are game to try a bit of HTML and inline SVG editing, you can try North Road (@ndawson)'s HTML Frame trick to generate curved text in Print Layouts at Creating circular insets and other fun QGIS layout tricks

You can also create curved text not tied to a data layer by using 'Create Text Annotation Along Line' which I think is from QGIS 3.32 onwards, although it doesn't match your needs really as it still has to be displayed in a map item in the print layout.

enter image description here

5

As @she_weeds already mentioned, you could use an SVG element inside a HTML frame. But unfortunately you have to play around with the path (arc) parameters until they fit: https://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle

enter image description here

<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300"
    xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 30 100 100"
    <!-- Path for text -->
    <path id="top-sector" style="fill:none;stroke:#92D050;stroke-width:18" d="M 10,50 A 80,70.5 0 0 1 80.5,50" />
    <text text-anchor="middle" dy="0.2em">
      <textPath xlink:href="#top-sector" startOffset="50%" style="font-family:Arial; font-size: 10px; font-weight:700;">Hello World</textPath>
    </text>
</svg>

We can simplify the search for the correct "d" parameter by using a custom Python expression function (credit for this function goes to @opsb):

import math

def polarToCartesian(centerX,centerY,radius,angleInDegrees):
  angleInRadians = (angleInDegrees-90) * math.pi / 180.0;

  return {
    'x': centerX + (radius * math.cos(angleInRadians)),
    'y': centerY + (radius * math.sin(angleInRadians))
  }

@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def describeArc(x, y, radius, startAngle, endAngle):
    start = polarToCartesian(x, y, radius, endAngle)
    end = polarToCartesian(x, y, radius, startAngle)

    if endAngle - startAngle <= 180:
       largeArcFlag = "0"
    else:
       largeArcFlag = "1"

    d = 'M %s %s A %s %s 0 %s 0 %s %s' % (start['x'], start['y'],radius, radius,largeArcFlag,end['x'],end['y'])
    return d

enter image description here enter image description here

And we could add some shadow to our SVG graphic:

enter image description here

<style>
svg { filter: drop-shadow(15px 20px 20px rgba(0,0,0,0.7)); }
</style>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" xmlns:xlink="http://www.w3.org/1999/xlink"  viewBox="20 -50 100 100">
    <!-- Path for text -->
    <path id="top-sector" style="fill:none;stroke:#92D050;stroke-width:18" d="[% describeArc( 150,0,100,290,250 )%]"  />
    <text text-anchor="middle" dy="0.2em">
      <textPath xlink:href="#top-sector" startOffset="50%" style="font-family:Arial; font-size: 10px; font-weight:700;">Hello World</textPath>
    </text>
</svg>

enter image description here

2
  • could you explain how to establish the various parameters of the expression, perhaps through an explanatory example? That is, the x and y coordinates are with respect to what? and the angles, where does 0 start?
    – pigreco
    Feb 25 at 16:50
  • 1
    @pigreco in SVG x,y start in the upper left corner with 0,0 and ascend to the lower right corner. Angle 0 points to the north, angle 90 points to the east.
    – christoph
    Feb 25 at 17:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.