#!/usr/bin/perl -CSDA # (c) Petr Baudis 2011, 2014 MIT licence # # Generate PDF with ballots. # Expects number of ballots as argument and names on stdin. use strict; use warnings; use utf8; # -- User configuration -- our @paper = (842, 598); # A4 landscape, 72dpi our @margin = (18, 18); our @ballot = (187, 36); our $filename = 'ballots.pdf'; our $fontface = 'Arial'; our $ffontface = 'Courier New'; # large, normal, small our @fontsize = (26, 11, 8); our @linespacing = (18, 13, 3); our $topmargin = 2; our $contact = 'brmelect: your votes are safe with us'; # -- User configuration end -- use lib 'perl'; use List::Util qw(min); use List::MoreUtils qw(pairwise); use Cairo; my $n_ballots = shift @ARGV; my $label = shift @ARGV; my @names; while (<>) { chomp; push @names, $_; } $ballot[1] += @names * ($linespacing[1] + $fontsize[1] + 9); our $surface = Cairo::PdfSurface->create ($filename, @paper); # Effective surface area our @surfsize = pairwise { $a - $b * 2 } @paper, @margin; # Grid layout on effective surface our @grid = pairwise { int($a / $b) } @surfsize, @ballot; # Grid surface area our @gridsurfsize = pairwise { $a * $b } @grid, @ballot; # Start of grid surface so that it is centered on the paper our @gridsurfstart = pairwise { ($a - $b) / 2 } @paper, @gridsurfsize; sub gen_token { my $token = ''; my @chars = ('a'..'z', '0'..'9'); for (1..5) { $token .= $chars[rand @chars]; } return($token); } # Produce a context for single ballot, starting at coordinates [0,0] sub ballot_cr { my ($surface, $cell) = @_; my @startM = pairwise { $a * $b } @ballot, @$cell; my @start = pairwise { $a + $b } @gridsurfstart, @startM; my $cr = Cairo::Context->create($surface); $cr->translate(@start); $cr->set_source_rgb(0, 0, 0); $cr->set_line_width(1); $cr; } # Centered text with top border at $$y. $size is index in font config above. sub ballot_text_centered { my ($cr, $y, $face, $slant, $weight, $size, $text) = @_; $$y += $linespacing[$size] / 2; $cr->select_font_face($face, $slant, $weight); $cr->set_font_size($fontsize[$size]); my $textents = $cr->text_extents($text); my $fextents = $cr->font_extents(); $$y += $fextents->{height}; $cr->move_to(($ballot[0] - $textents->{width}) / 2, $$y); $cr->show_text($text); $$y += $linespacing[$size] / 2; } sub ballot_text_plus_box { my ($cr, $y, $face, $slant, $weight, $size, $text) = @_; $$y += $linespacing[$size] / 2; $cr->select_font_face($face, $slant, $weight); $cr->set_font_size($fontsize[$size]); my $textents = $cr->text_extents($text); my $fextents = $cr->font_extents(); $$y += $fextents->{height}; $cr->move_to(45, $$y); $cr->show_text($text); $cr->rectangle(15, $$y - $fontsize[$size], 20, $fontsize[$size] * 3 / 2); $cr->stroke; $$y += $linespacing[$size] / 2; } sub ballot { my ($cr, $names) = @_; $cr->rectangle(0, 0, @ballot); $cr->stroke; my $ypos = $topmargin; # ballot_text($cr, \$ypos, $fontface, 'normal', 'bold', 0, $host); # ballot_text($cr, \$ypos, $ffontface, 'normal', 'normal', 1, $mac); for my $name (@$names) { ballot_text_plus_box($cr, \$ypos, $fontface, 'normal', 'normal', 1, $name); } $ypos += $linespacing[1] / 2; $cr->set_dash(1.0, 2.0); $cr->move_to(0, $ypos); $cr->line_to($ballot[0], $ypos); $cr->stroke; $cr->set_dash([1]); $ypos += $linespacing[2] / 2; my $tok = gen_token(); print($tok."\n"); ballot_text_centered($cr, \$ypos, $fontface, 'italic', 'normal', 2, $tok); ballot_text_centered($cr, \$ypos, $fontface, 'normal', 'bold', 2, $label); ballot_text_centered($cr, \$ypos, $fontface, 'italic', 'normal', 2, $contact); } my ($x, $y) = (0, 0); for my $n (1..$n_ballots) { ballot(ballot_cr($surface, [$x, $y]), \@names); $x++; if ($x >= $grid[0]) { $x = 0; $y++; if ($y >= $grid[1]) { my $cr = Cairo::Context->create($surface); $cr->show_page; ($x, $y) = (0, 0); } } }