Warping Vector Text With Ruby & Cairo

Starr Horne bio photo By Starr Horne

I recently needed to create arched text in an application that uses Cairo. Unfortunately, Cairo doesn’t support warping text out of the box.

Luckily, the pycairo guys were cool enough to include an example of warped text in their distribution.

Here it is, ported to ruby:

require 'cairo'
require 'rubygems'
require 'ruby-debug'

def warp_path(context, &block)
  first = true

  context.copy_path.each do |type, points|

    case type
    when Cairo::PATH_MOVE_TO
      if first
        context.new_path
        first = false
      end
      x, y = block.call(points[0].x, points[0].y)
      context.move_to(x, y)

    when Cairo::PATH_LINE_TO
      x, y = block.call(points[0].x, points[0].y)
      context.line_to(x, y)

    when Cairo::PATH_CURVE_TO
      x1, y1 = block.call(points[0].x, points[0].y)
      x2, y2 = block.call(points[1].x, points[1].y)
      x3, y3 = block.call(points[2].x, points[2].y)
      context.curve_to(x1, y1, x2, y2, x3, y3)

    when Cairo::PATH_CLOSE_PATH
      context.close_path()
    end
  end

end


def spiral(x, y, width, height)
  height = 512
  width = 512

  theta0 = -Math::PI * 3 / 4
  theta = x / width * Math::PI * 2 + theta0
  radius = y + 200 - x/7
  xnew = radius*Math.cos(theta)
  ynew = radius*Math.sin(-theta)
  [xnew + width/2, ynew + height/2]
end

def curl(x, y, width, height, text_width, text_height)
  xn = x - text_width/2
  # yn = y - text_height/2
  xnew = xn
  ynew = y + xn ** 3 / ((text_width/2)**3) * 70
  [xnew + width/2, ynew + height*2/5]
end


height = 800
width = 800

surface = Cairo::ImageSurface.new(width, height) 
c = Cairo::Context.new(surface)

c.set_font_size(80.0) 
c.move_to 0, 0
c.set_source_rgba [0,0,0,255]
c.text_path("ruby " * 5);
warp_path(c) { |x, y| spiral(x, y, 500, 500) }
c.fill


c.move_to 0, 300
c.text_path("I am curly");
extents = c.text_extents("I am curly")
warp_path(c) { |x, y| curl(x, y, width, height, extents.width, extents.height) }
c.fill

c.target.write_to_png("ruby_warped.png")