#!/usr/bin/perl -w use strict; our $us = $0; $us =~ s#.*/##; use POSIX; use Data::Dumper; our @paperpts = ('creditcard'); our $fontname_num = 'Helvetica'; our $fontscale_num = 0.8; our $fontname = 'Courier'; our $gapratio = 1.5; our $lineratio = 1; our $blankratio = 1.0; our @borders = (4,4); our @tiles = (1,1); our @lp_options = ( [ 'blank-below', 'blank-to-right', ], [ 'landscape', 'portrait' ], [ 'single-column', 'multi-column', ] ); our @lp_fixed; our $usage = <] ... options: -p for libpaper, or "creditcard", default is $paperpts[0] -f set both to same font name -f[[*]],[] default is $fontname_num*$fontscale_num,$fontname -b|-bx all in mm -T[x] default is $tiles[0]x$tiles[1] -g number-to-addr gap adjustment -l inter-line space ("leading") adjustment factor -B (blank space size) / (text size) -D debug END foreach my $spec (@lp_options) { $usage .= </dev/null" or die $!; sub badusage () { die "bad usage\n\n$usage"; } sub mm2pt { map { $_ * 72.0 / 25.4 } @_; } sub max { my $r = undef; foreach (@_) { $r = $_ if !defined $r or $_ > $r; } return $r; } sub min { my $r = undef; foreach (@_) { $r = $_ if !defined $r or $_ < $r; } return $r; } my $fontname_re = '[^()\\,]+'; my $dbl_re = '(?:[0-9]+\.?|[0-9]*\.[0-9]+)'; my $dom_re = '\@.*$'; for (;;) { badusage unless @ARGV; last unless $ARGV[0] =~ m/^-/; $_ = shift @ARGV; last if m/^--?$/; while (m/^-./) { if (s/^-p(\w+)$//) { @paperpts = ($1); } elsif (s/^-p($dbl_re)x($dbl_re)$//o) { @paperpts = mm2pt($1,$2); } elsif (s/^-f($fontname_re)$//o) { $fontname = $fontname_num = $1; } elsif (s/^-f(?:($fontname_re)(?:\*($dbl_re))?)?,($fontname_re)?$//o) { if (defined $1) { $fontname_num = $1; $fontscale_num = defined($2) ? $2 : 1.0; } if (defined $3) { $fontname = $3; } } elsif (s/^-b($dbl_re)$//o) { @borders = ($1,$1); } elsif (s/^-b($dbl_re)x($dbl_re)$//o) { @borders = ($1,$1); } elsif (s/^-l($dbl_re)$//o) { $lineratio = $1; } elsif (s/^-g($dbl_re)$//o) { $gapratio = $1; } elsif (s/^-B($dbl_re)$//o) { $blankratio = $1; } elsif (s/^-T(?:(\d+)x)(\d+)?$//o) { $tiles[0] = $1 || 1; $tiles[1] = $2; } elsif (s/^-D/-/) { open DEBUG, ">&STDERR" or die $!; } else { if (m/^--([-a-z]+)$/) { my ($lpi) = grep { grep { $1 eq $_ } @{ $lp_options[$_] } } 0..$#lp_options; if (defined $lpi) { $lp_fixed[$lpi] = $1; $_ = ''; next; } } badusage; } } } sub canonpaper ($) { my ($pts) = @_; return unless @$pts==1; my ($name) = @$pts; if ($name eq 'creditcard') { # ISO/IEC 7810 ID-1, from en.wikipedia.org/wiki/Payment_card @$pts = mm2pt qw(85.60 53.98); } else { $!=0; $?=0; my $r = `paperconf -sp $name`; defined $r or die "paperconf failed: $? $!\n"; $r =~ m/^([0-9.]+) ([0-9.]+)$/ or die "$_ ?"; @$pts = ($1,$2); } } canonpaper(\@paperpts); @borders = mm2pt @borders; @ARGV >= 2 or badusage; our @strings; our $domain_suffix; our $pertile; sub readstrings () { my $nlen = 0; open P, "-|", @ARGV or die $!; while (

) { chomp or die; m/^(\d+) (\S+)$/ or die "$_ ?"; $nlen = length($1) if length($1) > $nlen; push @strings, [ $1, $2 ]; } $!=0; $?=0; close P or die "$us: generator failed: $! $?\n"; @strings or die "$us: nothing to show\n"; if ($strings[0][1] =~ m/$dom_re/o) { my $dom = $&; if (!grep { $_->[1] !~ m/$dom_re/o && $& eq $dom } @strings) { $domain_suffix = $dom; $_->[1] =~ s/$dom_re//o foreach @strings; } } $pertile = ceil(@strings / ($tiles[0] * $tiles[1])); } our @lp_values; our @numbers_metr; our @texts_metr; our @domain_metr; our $gap_width; our $colgap_width; our @core_size; our @item_size; our $domain_vsize; our $rotate_paper; our @eff_paper_size; sub wontfit ($) { print DEBUG " NO @_\n"; return 0; } sub psstring ($) { local ($_) = @_; s/[()\\]/\\$&/g; return "($_)"; } our @numbers_1_metr; our @nom_gap_1_metr; our @texts_1_metr; our @domain_1_metr; sub prepare_metrics () { print DEBUG " prepare_metrics\n"; my $pchild = open GI, "-|"; defined $pchild or die $!; my @sets = ([ \@numbers_1_metr, $fontname_num, map { $_->[0] } @strings ], [ \@nom_gap_1_metr, $fontname_num, ' ' ], [ \@texts_1_metr, $fontname, map { $_->[1] } @strings ], ); if (defined $domain_suffix) { push @sets, [ \@domain_1_metr, $fontname, $domain_suffix ]; } if (!$pchild) { foreach my $set (@sets) { my ($ra, $fn, @s) = @$set; print DEBUG " want $fn ",scalar(@s),"\n"; printf <; defined or die "gs fail or eof"; printf DEBUG " %s (%d,$ix) |%s", $fn, $count, $_; chomp; m/^-?$dbl_re$/o or die "gs $_ ?"; push @tbb, $_; } print DEBUG " metrics $fn [$count]: @tbb\n"; $bb[0] = min $bb[0], $tbb[4]; $bb[1] = min $bb[1], $tbb[3]; $bb[2] = max $bb[2], $tbb[2]; $bb[3] = max $bb[3], $tbb[1]; $bb[4] = max $bb[4], $tbb[0]; } print DEBUG " metrics $fn: @bb\n"; @$ra = map { $_ * 0.1 } @bb; } $!=0; $?=0; close GO or die "gs $! $?"; $!=0; $?=0; close GI or die "gs paste $! $?"; } sub do_layout_recursive_search ($); sub do_layout_recursive_search ($) { my ($lpi) = @_; if ($lpi < @lp_options) { foreach my $v ($lp_fixed[$lpi] or @{ $lp_options[$lpi] }) { $lp_values[$lpi] = $v; my $r = do_layout_recursive_search $lpi+1; return $r if $r; } return 0; } print DEBUG " try", (map { sprintf " %-10.10s", $_ } @lp_values), ":"; my %lp_y; $lp_y{$_} = 1 foreach @lp_values; $lp_y{rotate_paper} = ($paperpts[0] > $paperpts[1] # paper looks like landscape xor $lp_y{'landscape'}); $eff_paper_size[$_] = $paperpts[$_] - 2.0*$borders[$_] foreach qw(0 1); @eff_paper_size = reverse @eff_paper_size if $lp_y{rotate_paper}; $eff_paper_size[0] >= $domain_metr[4] or return wontfit "domain suffix too long"; @item_size = @core_size; my $blank_coord = !!$lp_y{'blank-below'}; $item_size[$blank_coord] *= (1.0 + $blankratio); foreach my $coord (qw(0 1)) { my $avail = $eff_paper_size[$coord]; my $each = $item_size[$coord]; if (!$coord) { $each += $colgap_width; $avail += $colgap_width; } else { if (defined $domain_suffix) { $avail -= $domain_vsize; } } $lp_y{$coord} = floor($avail / $each); $lp_y{$coord} >= 1 or return wontfit "cannot fit even one $coord"; } if ($lp_y{'single-column'}) { $lp_y{0} = 1; } else { $lp_y{0} >= 2 or return wontfit "requested multi-column but only one"; } my $laycountshow = "$lp_y{0} $lp_y{1}"; $lp_y{0} * $lp_y{1} >= $pertile or return wontfit "layout fits too few $laycountshow"; print DEBUG " OK $laycountshow\n"; return \%lp_y; } sub do_layout ($) { my ($fontsize) = @_; print DEBUG "layout $fontsize\n"; @numbers_metr = map { $_ * $fontsize * $fontscale_num } @numbers_1_metr; $gap_width = $gapratio * $fontsize * $nom_gap_1_metr[4]; $colgap_width = $gap_width * 2; @texts_metr = map { $_ * $fontsize } @texts_1_metr; @domain_metr = map { $_ * $fontsize } @domain_1_metr; $core_size[0] = $numbers_metr[4] + $gap_width + $texts_metr[4]; $core_size[1] = $lineratio * $fontsize; $domain_vsize = $lineratio * $fontsize; return do_layout_recursive_search 0; } our $lp_y; sub determine_size_layout () { my $minsz; my $maxsz; my $usesz; for (;;) { my $trysz = !defined $minsz ? 1 : !defined $maxsz ? $minsz * 4 : sqrt($minsz * $maxsz); my $ok = do_layout $trysz; if ($ok) { $minsz = $trysz; } else { $maxsz = $trysz; } defined $minsz or die "cannot fit at even at ${trysz}pt\n"; if (defined $maxsz && ($maxsz / $minsz) < 1.001) { $usesz = $minsz; last; } } $lp_y = do_layout $usesz or die; $lp_y->{f} = $usesz; print DEBUG Dumper($usesz, \@lp_values, \@numbers_metr, \@texts_metr, $gap_width, $colgap_width, \@core_size, \@item_size, \@eff_paper_size, $lp_y); } sub prf { printf @_ or die $!; } sub write_output_tile ($) { my ($tile_string_off) = @_; my @cnr; my $c; for ($cnr[0]=0; $cnr[0]<2; $cnr[0]++) { for ($cnr[1]=0; $cnr[1]<2; $cnr[1]++) { foreach my $cnrc (qw(0 1)) { prf "newpath "; prf "%s ", $cnr[$_] ? $paperpts[$_] : 0 foreach qw(0 1); prf "moveto "; prf "%s ", ($cnrc!=$_ ? 0 : $cnr[$_] ? -1 : +1) * $borders[$_] foreach qw(0 1); prf "rlineto stroke\n"; } } } prf "%s ", $borders[$_] foreach qw(0 1); prf "translate\n"; if ($lp_y->{rotate_paper}) { prf "90 rotate\n"; prf "0 %s translate\n", -$eff_paper_size[1]; } my $rows = ceil($pertile / $lp_y->{0}); my @cell_size; $cell_size[0] = ($eff_paper_size[0] + $colgap_width) / $lp_y->{0}; $cell_size[1] = ($eff_paper_size[1] + (defined $domain_suffix ? -$domain_vsize : 0) ) / $rows; prf "/rightadjust { dup stringwidth pop neg 0 rmoveto } bind def\n"; foreach my $nums (qw(1 0)) { prf("%s findfont %s scalefont setfont\n", psstring($nums ? $fontname_num : $fontname), ($nums ? $fontscale_num : 1.0) * $lp_y->{f}); prf "%s setlinewidth\n", $gap_width * 0.1; print DEBUG "nums? $nums rows=$rows\n"; foreach my $col (0..$lp_y->{0}-1) { my $col_lhs = $cell_size[0] * $col; if ($col > 0 && $nums) { prf "newpath %s ", $col_lhs - $colgap_width*0.5; prf "%s moveto ", $eff_paper_size[1]; prf "0 %s rlineto ", -$cell_size[1] * $rows; prf "stroke\n"; } foreach my $row (0..$rows-1) { my $se = $strings[$col*$rows + $row + $tile_string_off]; next unless $se; prf "newpath "; prf "%s ", $col_lhs + ($nums ? $numbers_metr[4] : $numbers_metr[4] + $gap_width); prf "%s ", $eff_paper_size[1] - $cell_size[1] * $row - $core_size[1]; prf "moveto %s ", psstring($se->[!$nums]); if ($nums) { prf "rightadjust "; } # prf " gsave 10 10 rlineto stroke grestore\n"; prf "show\n"; } } if (!$nums) { if (defined $domain_suffix) { prf "%s 0 moveto", $eff_paper_size[0]; prf "%s ", psstring($domain_suffix); prf "rightadjust show\n"; } } } } sub write_output () { prf "%%!\n"; my @tile; for ($tile[0]=0; $tile[0]<$tiles[0]; $tile[0]++) { for ($tile[1]=0; $tile[1]<$tiles[1]; $tile[1]++) { prf "%% tile @tile\n"; prf "gsave\n"; prf "%s ", $tile[$_] * $paperpts[$_] foreach qw(0 1); prf "translate\n"; write_output_tile($tile[0] * $pertile + $tile[1] * $pertile * $tiles[0]); prf "grestore\n"; } } } readstrings(); prepare_metrics(); determine_size_layout(); write_output();