hidewords


#!/usr/bin/perl -w
#
# print (on a PostScript printer) one or more words "hidden"
# in an array of noise characters.
#

use strict;
use Getopt::Std;

my %opts;	# command line options
my ($x, $y);	# array indices
my ($i, $j);	# more array indices
my $c;		# a character
my ($total, $nlower, $nupper, $npunct, $ndigit, $nspace); # character counts
my $word;	# word from list of words to hide
my $r;		# random value
my @c;		# character 2-d array
my @used;	# keeps track of used positions in array
my ($xpos, $ypos); # random starting position in array
my ($dx, $dy);  # random direction from the starting position

getopts('P:', \%opts) || die "Usage: $0 [-P <printer>]\n";

#
# size of character array
#
my $ncols = 23;
my $nrows = 14;

#
# this is probably nonportable outside ASCIIland
#
my @upper = ('A'..'Z');
my @lower = ('a'..'z');
my @digit = ('0'..'9');
my @punct = qw(! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~);

#
# get list of words to hide
#
print "Enter one or more words to hide in the character array;\n";
print "Type ctrl-D when through:\n";

my @wlist = ();
while (<>) {
    chomp;
    push(@wlist, $_);
}

#
# erase screen so the magic words aren't left hanging.
#
system("clear");

#
# count different kinds of characters in words
#
$total = $nlower = $nupper = $npunct = $ndigit = $nspace = 0;
foreach $word (@wlist) {
    for ($i = 0; $i < length($word); $i++) {
	$c = substr($word, $i, 1);
	$total++;
	if ($c =~ /[[:lower:]]/) {
	    $nlower++;
	}
	elsif ($c =~ /[[:upper:]]/) {
	    $nupper++
	}
	elsif ($c =~ /[[:digit:]]/) {
	    $ndigit++;
	}
	elsif ($c =~ /[[:punct:]]/) {
	    $npunct++;
	}
	elsif ($c =~ /[[:space:]]/) {
	    $nspace++;
	}
	else { # ???
	    die "Sorry, I don't do characters like $c\n";
	}
    }
}

#
# generate character array with random characters, 
# with approximately the same distribution of character 
# classes as found in words to be hidden
#
for ($i = 0; $i < $ncols; $i++) {
    for ($j = 0; $j < $nrows; $j++) {
	$r = int(rand($total));
	if ($r < $nlower) {
	    $c[$i][$j] = $lower[rand(scalar @lower)];
	}
	elsif ($r < $nlower + $nupper) {
	    $c[$i][$j] = $upper[rand(scalar @upper)];
	}
	elsif ($r < $nlower + $nupper + $ndigit) {
	    $c[$i][$j] = $digit[rand(scalar @digit)];
	}
	elsif ($r < $nlower + $nupper + $ndigit + $npunct) {
	    $c[$i][$j] = $punct[rand(scalar @punct)];
	}
	else {
	    $c[$i][$j] = " ";
	}
	$used[$i][$j] = "N";
    }
}

#
# place the words into the array
#
foreach $word (@wlist) {
    #
    # try up to 100 times to put it somewhere
    #
    my $success = 0; my $tries = 0;
    while ($success == 0 && $tries < 100) {
	$tries++;
	#
	# pick a random starting point in the array
	# and a random direction
	#
	$xpos = int(rand($ncols));
	$ypos = int(rand($nrows));
	$dx = int(rand(3)) - 1;
	$dy = int(rand(3)) - 1;
	#
	# check for bad direction (going nowhere)
	#
	next if ($dx == 0 && $dy == 0);
	#
	# make sure we don't run off the edge of the array
	#
	next if ($xpos + $dx * length($word) >= $ncols);
	next if ($xpos + $dx * length($word) < 0);
	next if ($ypos + $dy * length($word) >= $nrows);
	next if ($ypos + $dy * length($word) < 0);
	#
	# and check to be sure we don't overwrite a previously-placed
	# word with a different character (same character is OK, though)
	#
	my $collide = 0;
	for ($i = 0; $i < length($word); $i++) {
	    $x = $xpos + $i * $dx;
	    $y = $ypos + $i * $dy;
	    if ($used[$x][$y] eq "Y" && $c[$x][$y] ne substr($word, $i, 1)) {
		$collide = 1;
		last;
	    }
	}
	next if $collide;
	#
	# seems to be OK to put it here...
	#
	for ($i = 0; $i < length($word); $i++) {
	    $x = $xpos + $i * $dx;
	    $y = $ypos + $i * $dy;
	    $c[$x][$y] = substr($word, $i, 1);
	    $used[$x][$y] = "Y";
	}
	$success = 1;
    }
    if ($success == 0) {
        die "Failed to place word, sorry\n";
    }
}

#
# output about credit card size...
#
my $ysize = int(2.125 * 72);
my $xsize = int(3.375 * 72); 
#
# center on 8.5x11 paper...
#
my $x0 = int((8.5 * 72  - $xsize)/2);
my $y0 = int((11.0 * 72 - $ysize)/2);

#
# output PostScript to the default (or specified) printer
#
open(PS, "|lpr" . $opt_P ? " -P $opt_P" : "");
# for debugging use this instead.
# open(PS, ">tmp.ps");

print PS "%!\n";
print PS "newpath\n";
print PS "$x0 $y0 moveto\n";
print PS "0 $ysize rlineto\n";
print PS "$xsize 0 rlineto\n";
print PS "0 -$ysize rlineto\n";
print PS "closepath\n";
print PS "2 setlinewidth\n";
print PS "stroke\n";

print PS "/Courier-Bold findfont\n";
print PS "11 scalefont\n";
print PS "setfont\n";
for ($i = 0 ; $i < $ncols; $i++) {
    for ($j = 0; $j < $nrows; $j++) {
	$xpos = $x0 + 8 + 10 * $i;
	$ypos = $y0 + 8 + 10 * $j;
	#
        # parens and backslashes need to be escaped in the PS
	#
	if ($c[$i][$j] eq "(" || $c[$i][$j] eq ")" || $c[$i][$j] eq "\\") {
	    print PS "$xpos $ypos moveto (\\$c[$i][$j]) show\n"
	}
	else {
	    print PS "$xpos $ypos moveto ($c[$i][$j]) show\n"
	}
    }
}
print PS "showpage\n";
close(PS);