#!/usr/bin/perl use strict; use warnings; use Glib qw(TRUE FALSE); use Pango; # This is a port of pango's cairotwisted example written by Behdad Esfahbod. sub two_points_distance { my ($a, $b) = @_; my $dx = $b->[0] - $a->[0]; my $dy = $b->[1] - $a->[1]; return sqrt($dx * $dx + $dy * $dy); } sub parametrize_path { my ($path) = @_; my $current_point; my @parametrization = (); foreach (0 .. $#{$path}) { my $data = $path->[$_]; $parametrization[$_] = 0.0; if ($data->{type} eq "move-to") { $current_point = $data->{points}[0]; } elsif ($data->{type} eq "line-to") { $parametrization[$_] = two_points_distance ($current_point, $data->{points}[0]); $current_point = $data->{points}[0]; } elsif ($data->{type} eq "curve-to") { $parametrization[$_] = two_points_distance ($current_point, $data->{points}->[0]); $parametrization[$_] += two_points_distance ($data->{points}[0], $data->{points}[1]); $parametrization[$_] += two_points_distance ($data->{points}[1], $data->{points}[2]); $current_point = $data->{points}[2]; } } return \@parametrization; } sub _fancy_cairo_stroke { my ($cr, $preserve) = @_; my $line_width = $cr->get_line_width; my $path = $cr->copy_path; $cr->new_path; $cr->save; $cr->set_line_width($line_width / 3); $cr->set_dash(0, 10, 10); foreach my $data (@{$path}) { my @points = @{$data->{points}}; if ($data->{type} eq "move-to" || $data->{type} eq "line-to") { $cr->move_to ($points[0][0], $points[0][1]); } elsif ($data->{type} eq "curve-to") { $cr->line_to ($points[0][0], $points[0][1]); $cr->move_to ($points[1][0], $points[1][1]); $cr->line_to ($points[2][0], $points[2][1]); } } $cr->stroke; $cr->restore; $cr->save; $cr->set_line_width ($line_width * 4); $cr->set_line_cap ("round"); foreach my $data (@{$path}) { my @points = @{$data->{points}}; if ($data->{type} eq "move-to") { $cr->move_to ($points[0][0], $points[0][1]); } elsif ($data->{type} eq "line-to") { $cr->rel_line_to (0, 0); $cr->move_to ($points[0][0], $points[0][1]); } elsif ($data->{type} eq "curve-to") { $cr->rel_line_to (0, 0); $cr->move_to ($points[0][0], $points[0][1]); $cr->rel_line_to (0, 0); $cr->move_to ($points[1][0], $points[1][1]); $cr->rel_line_to (0, 0); $cr->move_to ($points[2][0], $points[2][1]); } elsif ($data->{type} eq "close-path") { $cr->rel_line_to (0, 0); } } $cr->rel_line_to (0, 0); $cr->stroke; $cr->restore; foreach my $data (@{$path}) { my @points = @{$data->{points}}; if ($data->{type} eq "move-to") { $cr->move_to ($points[0][0], $points[0][1]); } elsif ($data->{type} eq "line-to") { $cr->line_to ($points[0][0], $points[0][1]); } elsif ($data->{type} eq "curve-to") { $cr->curve_to ($points[0][0], $points[0][1], $points[1][0], $points[1][1], $points[2][0], $points[2][1]); } elsif ($data->{type} eq "close-path") { $cr->close_path; } } $cr->stroke; if ($preserve) { $cr->append_path ($path); } } sub fancy_cairo_stroke { my ($cr) = @_; _fancy_cairo_stroke ($cr, FALSE); } sub fancy_cairo_stroke_preserve { my ($cr) = @_; _fancy_cairo_stroke ($cr, TRUE); } sub transform_path { my ($path, $f, $closure) = @_; foreach my $data (@{$path}) { if ($data->{type} eq "curve-to") { $f->($closure, $data->{points}[2]); $f->($closure, $data->{points}[1]); $f->($closure, $data->{points}[0]); } elsif ($data->{type} eq "move-to" || $data->{type} eq "line-to") { $f->($closure, $data->{points}[0]); } } } sub point_on_path { my ($param, $point) = @_; my $oldy = $point->[1]; my $d = $point->[0]; my $path = $param->{path}; my $parametrization = $param->{parametrization}; my ($ratio, $dx, $dy); my $data; my $current_point = undef; my $length = $#{$path}; my $i; for ($i = 0; $i < $length && $d > $parametrization->[$i]; $i++) { $d -= $parametrization->[$i]; $data = $path->[$i]; if ($data->{type} eq "move-to" || $data->{type} eq "line-to") { $current_point = $data->{points}[0]; } elsif ($data->{type} eq "curve-to") { $current_point = $data->{points}[2]; } } $data = $path->[$i]; if ($data->{type} eq "line-to") { my $ratio = $d / $parametrization->[$i]; $point->[0] = $current_point->[0] * (1 - $ratio) + $data->{points}[0][0] * $ratio; $point->[1] = $current_point->[1] * (1 - $ratio) + $data->{points}[0][1] * $ratio; $dx = -($current_point->[0] - $data->{points}[0][0]); $dy = -($current_point->[1] - $data->{points}[0][1]); $d = $oldy; $ratio = $d / $parametrization->[$i]; $point->[0] += -$dy * $ratio; $point->[1] += $dx * $ratio; } elsif ($data->{type} eq "curve-to") { $ratio = $d / $parametrization->[$i]; $point->[0] = $current_point->[0] * (1 - $ratio) * (1 - $ratio) * (1 - $ratio) + 3 * $data->{points}[0][0] * (1 - $ratio) * (1 - $ratio) * $ratio + 3 * $data->{points}[1][0] * (1 - $ratio) * $ratio * $ratio + 3 * $data->{points}[2][0] * $ratio * $ratio * $ratio; $point->[1] = $current_point->[1] * (1 - $ratio) * (1 - $ratio) * (1 - $ratio) + 3 * $data->{points}[0][1] * (1 - $ratio) * (1 - $ratio) * $ratio + 3 * $data->{points}[1][1] * (1 - $ratio) * $ratio * $ratio + 3 * $data->{points}[2][1] * $ratio * $ratio * $ratio; $dx =-3 * $current_point->[0] * (1 - $ratio) * (1 - $ratio) + 3 * $data->{points}[0][0] * (1 - 4 * $ratio + 3 * $ratio * $ratio) + 3 * $data->{points}[1][0] * ( 2 * $ratio - 3 * $ratio * $ratio) + 3 * $data->{points}[2][0] * $ratio * $ratio; $dy =-3 * $current_point->[1] * (1 - $ratio) * (1 - $ratio) + 3 * $data->{points}[0][1] * (1 - 4 * $ratio + 3 * $ratio * $ratio) + 3 * $data->{points}[1][1] * ( 2 * $ratio - 3 * $ratio * $ratio) + 3 * $data->{points}[2][1] * $ratio * $ratio; $d = $oldy; $ratio = $d / sqrt ($dx * $dx + $dy * $dy); $point->[0] += -$dy * $ratio; $point->[1] += $dx * $ratio; } } sub map_path_onto { my ($cr, $path) = @_; my $param = { path => $path, parametrization => parametrize_path ($path), }; my $current_path = $cr->copy_path; transform_path ($current_path, \&point_on_path, $param); $cr->new_path; $cr->append_path ($current_path); } sub draw_path { my ($cr) = @_; $cr->move_to (50, 700); $cr->line_to (300, 750); $cr->curve_to (550, 800, 900, 700, 900, 400); $cr->curve_to (900, 0, 600, 300, 100, 100); } sub draw_text { my ($cr) = @_; my $font_options = Cairo::FontOptions->create; $font_options->set_hint_style ("none"); $font_options->set_hint_metrics ("off"); $cr->set_font_options ($font_options); my $layout = Pango::Cairo::create_layout ($cr); my $desc = Pango::FontDescription->from_string ("Serif 72"); $layout->set_font_description ($desc); $layout->set_text ("It was a dream... Oh Just a dream..."); my $line = $layout->get_line (0); Pango::Cairo::layout_line_path ($cr, $line); } sub draw { my ($cr) = @_; # Decrease tolerance a bit, since it's going to be magnified $cr->set_tolerance (0.05); $cr->set_source_rgb (1.0, 0.0, 0.0); draw_path ($cr); fancy_cairo_stroke_preserve ($cr); my $path = $cr->copy_path_flat; $cr->new_path; draw_text ($cr); map_path_onto ($cr, $path); $cr->set_source_rgba (0.3, 0.3, 1.0, 0.3); $cr->fill_preserve; $cr->set_source_rgb (0.1, 0.1, 0.1); $cr->stroke; } { if ($#ARGV != 0) { warn "Usage: cairo-twisted-text.pl OUTPUT_FILENAME\n"; exit 1; } my $filename = $ARGV[0]; my $surface = Cairo::ImageSurface->create ("argb32", 500, 500); my $cr = Cairo::Context->create ($surface); $cr->translate (0, 50); $cr->scale (0.5, 0.5); $cr->set_source_rgb (1.0, 1.0, 1.0); $cr->paint; draw ($cr); if ("success" ne $surface->write_to_png ($filename)) { warn "Could not save png to '$filename'\n"; exit 1; } exit 0; }