#!/usr/bin/perl -w
my $RCS_Id = '$Id: album.pl,v 1.106 2007/06/16 12:37:56 jv Exp $ ';

# Author          : Johan Vromans
# Created On      : Tue Sep 15 15:59:04 2002
# Last Modified By: Johan Vromans
# Last Modified On: Sun Dec 28 16:07:01 2008
# Update Count    : 3266
# Status          : Unknown, Use with caution!

################ Common stuff ################

$VERSION = "1.50_08";

use strict;

# Package or program libraries, if appropriate.
# $LIBDIR = $ENV{'LIBDIR'} || '/usr/local/lib/sample';
# use lib qw($LIBDIR);
# require 'common.pl';

# Package name.
my $my_package = 'Sciurix';
# Program name and version.
my ($my_name, $my_version) = $RCS_Id =~ /: (.+).pl,v ([\d.]+)/;
# Tack '*' if it is not checked in into RCS.
$my_version .= '*' if length('$Locker:  $ ') > 12;

my $creator = qq{Created with <a href="http://search.cpan.org/~jv/Album/">Album</a> $::VERSION};

################ Command line parameters ################

use Getopt::Long 2.13;

# Command line options.
my $import_exif = 0;
my $import_dir;
my $update = 0;			# add new from large/import
our $dest_dir = ".";		# needs occasional 'local'
my $info_file;
my $linkthem = 1;		# link orig to large, if possible
my $clobber = 0;		# overwrite medium/thumbnails
my $mediumonly = 0;		# only medium size (for web export)
my $forcemedium = 0;		# force medium size if large is smaller
my $externalize_css = 0;	# create external css files
my $externalize_formats = 0;	# create external format files
my $select = 'default';		# select images
my $verbose = 1;		# verbose processing

# These are left undefined, for set_defaults. Note: our, not my.
our $index_columns;
our $index_rows;
our $thumb;
our $medium;			# medium size, between large and small
our $album_title;
our $caption;
our $datefmt;
our $icon;
our $locale;
our $lib_common;
our $home_link;

# These are not command line options.
my $journal;			# create journal
my $encoding;			# info_file encoding

# Development options (not shown with -help).
my $debug = 0;			# debugging
my $trace = 0;			# trace (show process)
my $test = 0;			# test mode.

# Process command line options.
app_options();

# Post-processing.
$trace |= ($debug || $test);
$dest_dir =~ s;^\./;;;
$import_dir =~ s;^\./;; if $import_dir;

################ Presets ################

use constant DEFAULTS => { info       => "info.dat",
			   title      => "Photo Album",
			   medium     => 0,
			   mediumsize => 915,
			   thumbsize  => 200,
			   indexrows  => 3,
			   indexcols  => 4,
			   caption    => "fct",
			   captionmin => "f",
			   dateformat => '%F',
			   icon	      => 0,
			 };

my $TMPDIR = $ENV{TMPDIR} || $ENV{TEMP} || '/usr/tmp';

my $picpat = qr{(?i:jpe?g|png|gif|nef)};
my $movpat = qr{(?i:mpe?g|mov|avi)};
my $xtrpat = qw{(?i:html?)};
my $suffixpat = qr{\.$picpat|$movpat};
my $xsuffixpat = qr{\.$picpat|$movpat|$xtrpat};

my %capfun = ('c' => \&c_caption,
	      'f' => \&f_caption,
	      's' => \&s_caption,
	      't' => \&t_caption,
	     );

my $br = br();

# Max.number of clickable index numbers (should be odd).
use constant IXLIST => 15;

# Stylesheets version.
my $css_major = 1;
my $css_minor = 0;

# Formats version.
my $fmt_major = 1;
my $fmt_minor = 0;

# Helper programs
my $prog_jpegtran  = findexec("jpegtran");
my $prog_mplayer   = findexec("mplayer");
my $prog_mencoder  = findexec("mencoder");

################ The Process ################

use File::Spec;
use File::Path;
use File::Basename;
use Time::Local;
use Image::Info;
use Image::Magick;
use Data::Dumper;
use POSIX qw(locale_h strftime);
use locale;

# The files already there, if any.
my $gotlist = new FileList;
# The files in the import dir, if any.
my $implist = new FileList;

# The list of files, in the order to be processed.
# This list is initialy filled from info.dat, and (optionally) updated
# from the other lists.
my $filelist = new FileList;

# This is the list of all entries to be journalled (all images, plus
# possible interspersed loose annotations).
my @journal;

# Load cached info, if possible.
load_cache();

# Load image names and info from the info file, if any.
# This produces the initial file list.
load_info();
#print STDERR Data::Dumper->Dump([$filelist],[qw(filelist)]);

# Load image names and info for files we already got.
load_files()  if -d d_large();
#print STDERR Data::Dumper->Dump([$gotlist],[qw(gotlist)]);

# Load image names and info for files we can import.
load_import() if $import_dir && -d $import_dir;
#print STDERR Data::Dumper->Dump([$implist],[qw(implist)]);

# Apply defaults to unset parameters.
set_defaults();

# warn("date => ", strftime($datefmt, localtime(time)), "\n");

# Verify and update the file list.
my $added = update_filelist();

# Perform selection. Normally, hidden entries are ignored.
# Option --select=all overrides this.
$filelist = $filelist->filter($select);

#print STDERR Data::Dumper->Dump([$filelist],[qw(filelist)]);

my $num_entries = $filelist->tally;
print STDERR ("Number of entries = $num_entries",
	      $added ? " ($added added)" : "",
	      "\n") if $verbose > 1;
die("Nothing to do?\n") unless $num_entries > 0;
exit(0) if $test;

# Clean up and create directories.
if ( $clobber ) {
    rmtree([d_index(), d_medium()], $verbose > 1);
    rmtree([d_journal()], $verbose > 1);
}
mkpath([d_index(), d_large(), d_icons()], $verbose > 1);
mkpath([d_medium()], $verbose > 1) if $medium;

# Copy images in place, rotate if necessary, and create the thumbnails.
prepare_images();

# Update cache.
update_cache();
my $cache_update = 0;

my $entries_per_page = $index_columns*$index_rows;
my $num_indexes = int(($num_entries - 1) / $entries_per_page) + 1;

my $fn = "img0000";
# Cleanup excess files.
for ( 0 ) {
    my $excess = $fn++ . ".html";
    unlink(d_medium($excess));
    unlink(d_large($excess)) or last;
}

# Map file names to html pages. Start with 1 to match "image N of M".
my @htmllist;
for my $i ( 0 .. $num_entries-1 ) {
    $htmllist[$i] = $fn++ . ".html";
}

# Cleanup excess files.
for (my $i = $num_entries ; ; $i++ ) {
    my $excess = $fn++ . ".html";
    unlink(d_medium($excess));
    unlink(d_large($excess)) or last;
}

# Copy the button images over to the target directory.
add_button_images();

# Init formats and stylesheets.
init_formats();
init_stylesheets();

# Write the individual pages.
write_image_pages();

# Write the index pages.
write_index_pages();

# Write the journal.
write_journal_pages();

# Create index & icon.
create_master_index();
create_index_icon();

# Final update, if needed.
update_cache() if $cache_update;

exit 0;

################ Subroutines ################

# Image types.
use constant T_JPG    => 1;
use constant T_MPG    => 2;
use constant T_VOICE  => 3;	# still image + sound
# Pseudo types.
use constant T_PSEUDO => 0;
use constant T_TAG    => -1;
use constant T_ANN    => -2;
use constant T_REF    => -3;

# List of possible subdirs to process.
my @subdirs;

# Journal tags
my %jnltags;


# fjoin is used for generating file names.
sub fjoin	 { File::Spec->catfile(@_); }

# hjoin is used for generating html paths
sub hjoin	 { join("/", @_); }

# $fjoin will be dynamically switched depending on context.
our $fjoin; INIT { $fjoin = \&fjoin }

sub d_dest       { unshift(@_, $dest_dir) unless $dest_dir eq ".";
		   $fjoin->(@_); }
sub d_index      { unshift(@_, "index");      goto &d_dest; }
sub d_large      { unshift(@_, "large");      goto &d_dest; }
sub d_medium     { unshift(@_, "medium");     goto &d_dest; }
sub d_journal    { unshift(@_, "journal");    goto &d_dest; }
sub d_up	 { unshift(@_, "..");         goto &d_dest; }

sub d_destc      { unshift(@_, $lib_common) if $lib_common; goto &d_dest; }
sub d_icons      { unshift(@_, "icons");      goto &d_destc; }
sub d_css        { unshift(@_, "css");        goto &d_destc; }
sub d_fmt        { unshift(@_, "formats");    goto &d_destc; }

my %optcfg;			# option set from config files

sub setopt {
    no strict qw(refs);
    return if defined(${$_[0]});
    print STDERR ("setopt $_[0] -> $_[1]\n") if $trace;
    ${$_[0]} = $_[1];
    $optcfg{$_[0]} = 1;
}

sub parse_line {
    local ($_) = (@_);
    my $err = 0;

    if ( /^!?\s*(\S.*)/ ) {
	$_ = $1;
	if ( /^title\s+(.*)/ ) {
	    setopt("album_title", $1);
	}
	elsif ( /^page\s+(\d+)x(\d+)/ ) {
	    setopt("index_rows", $1);
	    setopt("index_columns", $2);
	}
	elsif ( /^thumbsize\s*(\d+)/ ) {
	    setopt("thumb", $1);
	}
	elsif ( /^mediumsize\s*(\d+)(!)?/ ) {
	    setopt("medium", $1);
	    $forcemedium = defined $2;
	}
	elsif ( /^medium\s*(-?\d+)?/ ) {
	    setopt("medium", $1 || DEFAULTS->{mediumsize});
	}
	elsif ( /^dateformat\s*(.*)/ ) {
	    setopt("datefmt", $1);
	}
	elsif ( /^caption\s*(.*)/ ) {
	    setopt("caption", $1);
	}
	elsif ( /^icon\s*(.*)/ ) {
	    setopt("icon", defined($1) && length($1) ? $1 : 1);
	}
	elsif ( /^locale\s*(.*)/ ) {
	    setopt("locale", $1);
	}
	elsif ( /^depth\s+(\d+)/ ) {
	    # lib_common is used in the HTML, don't use fjoin.
	    setopt("lib_common", join("/", ("..") x $1));
	}
	elsif ( /^home\s+(.+)/ ) {
	    setopt("home_link", $1);
	}
	else {
	    warn("Unknown control: $_[0]\n");
	    $err++;
	}
    }
    else {
	warn("Invalid control: $_[0]\n");
	$err++;
    }
    $err;
}

sub set_defaults {
    # Load settings from user files.
    my $sl;
    unless ( $sl = $ENV{ALBUMCONFIG} ) {
	$sl = ".albumrc";
	$sl .= ":".$ENV{HOME}."/.albumrc" if $ENV{HOME};
    }
    foreach my $cf ( split(/:/, $sl) ) {
	unless ( -f $cf ) {
	    warn("$cf: $!\n") if $ENV{ALBUMCONFIG};
	    next;
	}
	open(my $fh, "<", $cf) || next;
	warn("parsing: $cf\n") if $trace;
	my $err = 0;
	while ( <$fh> ) {
	    next if /^\s*#/;
	    next unless /\S/;
	    $err += parse_line($_);
	}
	close($fh);
	die("Errors in config file $cf, aborted\n") if $err;
    }

    # Finally, apply defaults if necessary.
    warn("apply defaults\n") if $trace;
    setopt("album_title",   DEFAULTS->{title});
    setopt("index_rows",    DEFAULTS->{indexrows});
    setopt("index_columns", DEFAULTS->{indexcols});
    setopt("thumb",         DEFAULTS->{thumbsize});
    setopt("datefmt",       DEFAULTS->{dateformat});
    setopt("icon",          DEFAULTS->{icon});

    $medium = DEFAULTS->{mediumsize} if defined($medium) && !$medium || $mediumonly;
    $medium = 0 if defined($medium) && $medium < 0;

    # Caption values.
    setopt("caption", DEFAULTS->{( -s $info_file || $import_dir) ?
				 "caption" : "captionmin" });
    die("Invalid value for caption: $caption\n")
      unless $caption =~ /^[fsct]*$/i;
    $caption = lc($caption);

    if ( $locale ) {
	setlocale(LC_TIME, $locale);
	setlocale(LC_COLLATE, $locale);
    }

    if ( defined($lib_common) ) {
	$lib_common =~ s;/+$;;;
    }
    $lib_common ||= "";
}

sub load_info {
    my %typemap = ( 'p' => T_JPG, 'm' => T_MPG, 'v' => T_VOICE );

    # If an info has been supplied, it'd better exist.
    if ( $info_file ) {
	die("$info_file: $!\n") unless -s $info_file;
    }
    else {
	# Try default.
	$info_file = d_dest(DEFAULTS->{info});
	unless ( -s $info_file ) {
	    my $add_new; $add_new++ if $import_dir;
	    my $add_src; $add_src++ if -d d_large();
	    print STDERR ("No ", d_dest(DEFAULTS->{info}));
	    print STDERR (", adding images from ") if $add_src || $add_new;
	    print STDERR (d_large())               if $add_src;
	    print STDERR (" and ")                 if $add_src && $add_new;
	    print STDERR ($import_dir)             if $add_new;
	    print STDERR ("\n");
	    return;
	}
    }

    my $err = 0;
    my $file;
    my $tag;

    my $fh = do { local *FH; *FH };
    die("$info_file: $!\n")
      unless open($fh, "<", $info_file);
    warn("parsing: $info_file\n") if $trace;

    my $el;
    my %dirs;

    while ( <$fh> ) {
	chomp;

	# Detection of condig system for info_file.
	# Uses GNU Emacs syntax, e.g.,
	#  # blah        -*- mode: album; coding: utf-8 -*-
	if ( $. == 1
	     &&
	     m/^\s*\#			# start with #
	      .*			# arb
	      -\*-			# -*-
	      (?:.*?;)*			# things, must be ; terminated
	      \s*			# ws
	      coding\s*:\s*([\w\d-]+)	# coding: utf-8
	      \s*			# ws
	      (?:;.*)*			# things, must be ; started
	      -\*-			# -*-
	      /x ) {
	    $encoding = $1;
	    warn("using encoding $encoding for $info_file\n") if $trace;

	    # Remember position, reopen and restart IO.
	    my $pos = tell($fh);
	    close($fh);
	    open($fh, "<:encoding($encoding)", $info_file)
	      or die("$info_file: $!\n");
	    seek($fh, $pos, 0);
	    next;
	}

	next if /^\s*#/;
	next unless /\S/;

	if ( /^\s+/ && $el ) {
	    $el->description($el->description . "\n" . $_);
	    next;
	}

	if ( /^!\s*(\S.*)/ ) {
	    $_ = $1;
	    if ( /^tag\s*(.*)/ ) {
		$tag = $1;
		$tag =~ s/\s$//;
		$tag =~ s/\s+/ /g;
	    }
	    elsif ( /^subdirs\s*(.*)/ ) {
		foreach ( split(' ', $1)) {
		    $dirs{$_}++;
		}
	    }
	    elsif ( /^journal\s*(.*)/ ) {
		if ( $filelist->tally ) {
		    warn("\"!journal\" must precede image info\n");
		    $err++;
		}
		load_info_journal($err, $fh);
		return;
	    }
	    else {
		$err += parse_line("!".$_);
	    }
	    next;
	}

	($file, $a) = $_ =~ /^(.+?$xsuffixpat)\s*(.*)/;
	($file, $a) = $_ =~ /^([^\s]+)\s+(.*)/ unless defined($file);

	my $rotate;
	my $type = T_JPG;
	my $assc;
	while ( $a && $a =~ /^-(\w):(\S+)\s*(.*)/ ) {
	    if ( lc($1) eq 'o' ) {
		$rotate = 90 * ($2 % 4);
	    }
	    elsif ( lc($1) eq 'i' ) {
		$assc = fjoin(basename($file), $2);
		unless ( -s $assc && -r _ ) {
		    warn("$file (info): $assc [$!]\n");
		    undef $assc;
		}
	    }
	    elsif ( lc($1) eq 't' ) {
		$type = $typemap{lc($2)}
		  or warn("$file (info): Illegal type: $2\n"), $err++;
	    }
	    $a = $3;
	}
	$el = new ImageInfo($file);
	$el->type($type);
	$el->description($a) if $a;
	$el->tag($tag) if $tag;
	$el->_rotation($rotate) if defined($rotate);
	if ( $file =~ /^(.+)\.$movpat$/i ) {
	    $el->type(T_MPG);
	    $el->assoc_name($1."s.jpg"); # associates still image
	}
	elsif ( $type == T_VOICE ) {
	    (my $t = $file) =~ s/\.jpg$/.mp3/i;
	    $el->assoc_name($t);
	}
	elsif ( $file =~ /.\.html?$/i ) {
	    $type = T_REF;
	}
	elsif ( -d $file ) {
	    $type = T_REF;
	    my $f = $file;
	    $file = fjoin($f, "index", "index0001.html");
	    $file = fjoin($f, "index.html") unless -s $file;
	}
	if ( $type == T_REF ) {
	    for ( fjoin(dirname($file), "icon.jpg") ) {
		$assc = $_ if !defined $assc && -f $_;
	    }
	    $assc = d_icons("extern.jpg") unless defined $assc;
	    $el->assoc_name($assc);
	    $el->dest_name($file);
	    $el->type($type);
	}
	$filelist->add($el);
	$dirs{$1} = 1 if $type != T_REF && $file =~ m;^(.+)[/\\][^/\\]+$;;
    }
    close($fh);
    die("Aborted\n") if $err;
    @subdirs = sort(keys(%dirs));
}

sub load_info_journal {
    my $err = shift;
    my $fh = shift;

    #### WARNING: EXPERIMENTAL ####

    warn("parsing (journal mode)\n") if $trace;

    my %typemap = ( 'p' => T_JPG, 'm' => T_MPG, 'v' => T_VOICE );

    my $tag;
    my $nexttag = 0;
    my $annotation = "";
    my $tags = 0;
    my %dirs;
    local($/) = "";		# para mode
    while ( <$fh> ) {
	chomp;
	next if /^\s*#/;
	next unless /\S/;

	# Handle controls.
	if ( /^!\s*(\S.*)/ ) {
	    $_ = $1;
	    if ( /^tag\s*(.*)/ ) {
		$tag = $1;
		$tag =~ s/\s$//;
		$tag =~ s/\s+/ /g;

		if ( $tag !~ /\S/ ) {
		    warn("Tag may not be empty\n");
		    $err++;
		    next;
		}
		if ( exists($jnltags{$tag}) ) {
		    warn("Tag \"$tag\" is not unique\n");
		    $err++;
		}
		$jnltags{$tag} = sprintf("%04d", ++$nexttag);
		my $el = new ImageInfo;
		$el->tag($tag);
		$el->type(T_TAG);
		push(@journal, $el);
		$tags++;
	    }
	    elsif ( /^subdirs\s*(.*)/ ) {
		foreach ( split(' ', $1)) {
		    $dirs{$_}++;
		}
	    }
	    elsif ( /^journal\s*(.*)/ ) {
		if ( $filelist->tally ) {
		    warn("\"!journal\" must precede image info\n");
		    $err++;
		}
		# Ignore.
	    }
	    else {
		$err += parse_line("!".$_);
	    }
	    next;
	}

	if ( /^\*\s*(.*)/s ) {
	    $_ = $1;
	}
	else {
	    my $el = new ImageInfo;
	    $el->annotation($_);
	    $el->tag($tag);
	    $el->type(T_ANN);
	    push(@journal, $el);
	    next;
	}
	s/\s*\n\s+/ /g;
	my @a = split(/\n/, $_);
	$_ = shift(@a);
	my $annotation = join(" ", @a);

	my ($file, $a) = $_ =~ /^(.+?$xsuffixpat)\s*(.*)/;

	my $rotate;
	my $type = T_JPG;
	my $assc;
	while ( $a && $a =~ /^-(\w):(\S+)\s*(.*)/ ) {
	    if ( lc($1) eq 'o' ) {
		$rotate = 90 * ($2 % 4);
	    }
	    elsif ( lc($1) eq 'i' ) {
		$assc = fjoin(basename($file), $2);
		unless ( -s $assc && -r _ ) {
		    warn("$file (info): $assc [$!]\n");
		    undef $assc;
		}
	    }
	    elsif ( lc($1) eq 't' ) {
		$type = $typemap{lc($2)}
		  or warn("$file (info): Illegal type: $2\n"), $err++;
	    }
	    $a = $3;
	}
	my $el = new ImageInfo($file);
	$el->type($type);
	$el->description($a) if $a;
	$el->tag($tag) if $tag;
	# $annotation ||= $a;
	if ( $annotation ) {
	    $annotation =~ s/^\s+//;
	    $annotation =~ s/\s+$//;
	    $annotation =~ s/\s+/ /g;
	    $el->annotation($annotation);
	}

	$el->_rotation($rotate) if defined($rotate);
	if ( $file =~ /^(.+)\.$movpat$/i ) {
	    $el->type(T_MPG);
	    $el->assoc_name($1."s.jpg"); # associates still image
	}
	elsif ( $type == T_VOICE ) {
	    (my $t = $file) =~ s/\.jpg$/.mp3/i;
	    $el->assoc_name($t);
	}
	elsif ( -d $file ) {
	    $type = T_REF;
	    my $f = $file;
	    $file = fjoin($f, "index", "index0001.html");
	    $file = fjoin($f, "index.html") unless -s $file;
	}
	elsif ( $file =~ /.\.html?$/i ) {
	    $type = T_REF;
	}
	if ( $type == T_REF ) {
	    for ( fjoin(dirname($file), "icon.jpg") ) {
		$assc = $_ if !defined $assc && -f $_;
	    }
	    $assc = d_icons("extern.jpg") unless defined $assc;
	    $el->assoc_name($assc);
	    $el->dest_name($file);
	    $el->type($type);
	}

	if ( $type > T_PSEUDO ) {
	    my @a = ($annotation);
	    my $pi = scalar(@journal) - 1;
	    while ( $pi >= 0 ) {
		my $e = $journal[$pi];
		last if $e->type != T_ANN;
		push(@a, $e->annotation);
		$pi--;
	    }
	    $el->annotation([@a]) if @a;
	}

	$filelist->add($el);
	push(@journal, $el) if !$a || $a !~ /^--/;

	$dirs{$1} = 1 if $type != T_REF && $file =~ m;^(.+)[/\\][^/\\]+$;;

    }
    close($fh);
    die("Aborted\n") if $err;
    @subdirs = sort(keys(%dirs));
    $journal = $tags;		# no tags -- no journal...
}

sub load_files {
    my $dh = do { local *DH; *DH; };
    opendir($dh, d_large())
      or die("Cannot opendir " . d_large() . ": $!\n");
    my @files = sort grep { !/^\./ && /$suffixpat$/ } readdir($dh);
    closedir($dh);

    foreach my $dir ( @subdirs ) {
	opendir($dh, d_large($dir))
	  or die("Cannot opendir " . d_large($dir) . ": $!\n");
	push(@files,
	     map { "$dir/$_" }
	         sort grep { !/^\./ && /$suffixpat$/ } readdir($dh));
	closedir($dh);
    }

    while ( @files ) {
	my $f = shift(@files);
	next unless -f d_large($f);
	my $el = new ImageInfo(d_large($f));
	$el->type(T_JPG);
	if ( $f =~ /^(.+)\.$picpat$/ ) {
	    my $m = "$1.mp3";
	    if ( -s d_large($m) ) {
		$el->type(T_VOICE);
		$el->assoc_name($m);
		warn(d_large($f).": Changed to VOICE\n") if $verbose;
	    }
	}
	elsif ( $f =~ /^(.+)\.$movpat$/i ) {
	    $el->type(T_MPG);
	    my $assoc = $1."s.jpg";
	    $el->assoc_name($assoc);
	    if ( @files && $files[0] eq $assoc ) {
		shift(@files);
		warn(d_large($assoc).": Skipped still\n") if $verbose;
	    }
	}
	$gotlist->add($el, $f);
    }
}

sub load_import {
    my $dh = do { local *DH; *DH; };
    opendir($dh, $import_dir)
      or die("Cannot opendir $import_dir: $!\n");

    my @files = sort grep { !/^\./ && /$suffixpat$/ } readdir($dh);
    closedir($dh);

    while ( @files ) {
	my $f = shift(@files);
	next unless -f fjoin($import_dir, $f);

	my $el = new ImageInfo(fjoin($import_dir, $f));
	if ( $import_exif ) {
	    shift(@files) if handle_exif($f, $files[0], $el);
	}
	else {
	    $el->type(T_JPG);
	    if ( $f =~ /^(.+)\.$movpat$/i ) {
		$el->type(T_MPG);
		$el->assoc_name($1."s.jpg");
	    }
	    $implist->add($el, $f);
	}
    }
}

sub handle_exif {
    my ($file, $next, $el) = @_;

    # Sony DSC-V1 produces the following files:
    #   DSC0nnnn.JPG	still image
    #   DSC0nnnn.JPE	mail mode image*
    #   DSC0nnnn.MPG	voice mode image*
    #   DSC0nnnn.TIF	uncompressed image*
    #   CLP0nnnn.GIF	clip motion file
    #   CLP0nnnn.HTM	clip motion file index
    #   MBL0nnnn.GIF	clip motion file, mobile mode
    #   MBL0nnnn.HTM	clip motion file index, mobile mode
    #   MOV0nnnn.MPG	movie
    # Files marked with * have a normal still image associated.

    # Normal still image.
    if ( $file =~ /^(.{4})(\d{4})\.($picpat)$/i ) {
	my ($type, $seq, $ext) = ($1, $2, $3);
	my $fd = $el->DateTime || "";
	if ( $fd =~ /(\d\d\d\d):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)/ && $2 != 0) {
	    my $time = timelocal($6,$5,$4,$3,$2-1,$1);
	    my $new = "$1$2$3$4$5$6$seq";
	    my $ii = cache_entry("$new.$ext");
	    if ( $ii && !$ii->orig_name ) {
		$ii->orig_name(fjoin($import_dir, $file));
	    }

	    $el->type(T_JPG);
	    $el->dest_name("$new.$ext");
	    $el->timestamp($time);
	    $file = "$new.$ext";
	    cache_entry($file, $el) unless $ii;
	}
	else {
	    warn(fjoin($import_dir, $file).": Missing or unparsable file date [$fd]\n")
	      if $verbose;
	    $el->type(T_JPG);
	}
	if ( $next && $next eq "$type$seq.mpg" ) {
	    warn(fjoin($import_dir, $file).": Changed to VOICE\n") if $verbose;
	    $el->type(T_VOICE);
	    (my $t = $file) =~ s/\.jpg$/.mp3/i;
	    $el->assoc_name($t);
	    $implist->add($el);
	    return 1;
	}
	$implist->add($el);
    }

    # MPEG movie.
    elsif ( $file =~ /^(.{4})(\d{4})\.($movpat)$/i ) {
	my ($type, $seq, $ext) = ($1, $2, $3);
	# We have to trust the file date...
	my $time = $el->timestamp;
	my @tm = localtime($time);
	my $new = sprintf("%04d%02d%02d%02d%02d%02d$seq",
			  1900+$tm[5], 1+$tm[4], @tm[3,2,1,0]);
	my $ii = cache_entry("$new.$ext");
	if ( $ii && !$ii->orig_name ) {
	    $ii->orig_name(fjoin($import_dir, $file));
	}

	$el->type(T_MPG);
	$el->dest_name("$new.$ext");
	$el->assoc_name($new."s.jpg");
	$implist->add($el, "$new.$ext");
	$file = "$new.$ext";
	cache_entry($file, $el) unless $ii;
    }

    # Assume ordinary JPEG or some picture.
    elsif ( $file =~ /^.*$picpat$/) {
	$el->type(T_JPG);
	$el->orig_name(fjoin($import_dir, $file));
	$el->dest_name($file);
	$implist->add($el, $file);
    }

    # Assume ordinary MPEG or some movie.
    elsif ( $file =~ /^(.*)($movpat)$/) {
	$el->type(T_MPG);
	$el->orig_name(fjoin($import_dir, $file));
	$el->dest_name($file);
	$el->assoc_name($1."s.jpg");
	$implist->add($el, $file);
    }
    return 0;
}

sub update_filelist {
    my $todo = new FileList;

    my $el;
    my %seen;
    my $missing;
    my $prev;

    foreach $el ( $filelist->entries ) {
	my $f = $el->dest_name;
	$seen{$f}++;
	print STDERR ("todo[inf]: $f") if $trace;
	my $entry = $gotlist->byname($f);
	if ( $entry ) {
	    print STDERR (" -- got") if $trace;
	}
	elsif ( $entry = $implist->byname($f) ) {
	    print STDERR (" -- imp") if $trace;
	}
	elsif ( $el->type == T_REF ) {
	    $entry = $el;
	    print STDERR (" -- ref") if $trace;
	}

	if ( $entry ) {
	    if ( $el->description =~ /^--(?:$|\s+)(.*)/ ) {
		$entry->description($1);
		$entry->hidden(1);
		print STDERR (" (hidden)") if $trace;
	    }
	    else {
		$entry->description($el->description);
	    }
	    # Copy properties from info.
	    $entry->tag($el->tag);
	    $entry->annotation($el->annotation);
	    $entry->_rotation($el->_rotation);
	    # Add and create prev/next links.
	    $entry->prev($prev->seq) if $prev;
	    $todo->add($entry);
	    $prev->next($entry->seq) if $prev;
	    print STDERR ("\n") if $trace;
	}

	else {
	    if ( $trace ) {
		print STDERR ("\n");
	    }
	    else {
		unless ( $el->description =~ /^--($|\s)/ ) {
		    print STDERR ("todo[inf]: $f -- missing\n");
		}
	    }
	    unless ( $el->description =~ /^--($|\s)/ ) {
		$missing++;
	    }
	}
	$prev = $entry if $entry && $entry->type != T_REF;
    }
    die("Aborted!\n") if $missing;

    unless ( $filelist->tally == 0 || $update ) {
	$filelist = $todo;
	return 0;
    }

    my $newinfo = "";
    my $date;
    my $new;

    foreach $el ( $gotlist->entries ) {
	my $f = $el->dest_name;
	print STDERR ("todo[got]: $f") if $trace;
	if ( $seen{$f}++ ) {
	    print STDERR (" -- seen\n") if $trace;
	    next;
	}
	print STDERR (" -- added\n") if $trace;
	my $nd = "";
	if ( $f =~ /^(\d{4})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/ ) {
	    my $tl = timelocal($6,$5,$4,$3,$2-1,$1);
	    $nd = strftime($datefmt, localtime($tl));
	    $el->timestamp($tl);
	}
	if ( !defined($date) || $nd ne $date ) {
	    $newinfo .= "\n!tag $nd\n";
	    $newinfo .= "\n" if $journal;
	    $date = $nd;
	}
	$newinfo .= defined $journal ? "* $f\n\n" : "$f\n";
	$el->tag($date) if $date;
	$el->prev($prev->seq) if $prev;
	$todo->add($el);
	$prev->next($el->seq) if $prev;
	$prev = $el unless $el->type == T_REF;
	push(@journal, $el) if $journal;
	$new++;
    }

    foreach $el ( $implist->entries ) {
	my $f = $el->dest_name;
	print STDERR ("todo[imp]: $f") if $trace;
	if ( $seen{$f}++ ) {
	    print STDERR (" -- seen\n") if $trace;
	    next;
	}
	print STDERR (" -- added\n") if $trace;
	my $nd = "";
	my $time = $el->timestamp;
	if ( $time ) {
	    $nd = strftime($datefmt, localtime($time));
	}
	if ( !defined($date) || $nd ne $date ) {
	    $newinfo .= "\n!tag $nd\n";
	    $newinfo .= "\n" if $journal;
	    $date = $nd;
	}
	$newinfo .=
	  (defined $journal ? "* " : "") .
	    "$f " .
	      ($el->rotation ? ("-O:".int($el->rotation/90)." ") : "") .
		($el->type == T_VOICE ? "-T:V " : "") .
		  ($journal ? " \n\n" : " \n");
	$el->tag($date) if $date;
	$el->prev($prev->seq) if $prev;
	$todo->add($el);
	$prev->next($el->seq) if $prev;
	$prev = $el unless $el->type == T_REF;
	push(@journal, $el) if $journal;
	$new++;
    }

    $filelist = $todo;

    unless ( $new ) {		# nothing to add
	warn("No new images imported\n") if $verbose > 1;
	return 0;
    }

    return $new if $test;

    unless ( -w $info_file ) {
	warn("$info_file: Cannot update (".
	     (-e _ ? "no write access" : "does not exist") .
	     ")\n") if $verbose;
	return $new;
    }

    my $infosize = -s $info_file;

    # Append new info.
    warn("Updating $info_file\n") if $verbose > 1;
    my $fh = do { local *F; *F };
    open($fh, $encoding ? ">>:encoding($encoding)" : ">>", $info_file) || die("$info_file: $!\n");
    unless ( $infosize ) {
	print $fh ("# album control file created by Album $::VERSION, ".
	       localtime(time), "\n\n");
	print $fh ("!title $album_title\n") if $album_title;
	if ( $medium && !$optcfg{"medium"} ) {
	    print $fh ($medium != DEFAULTS->{mediumsize} ?
		       "!mediumsize $medium\n" : "!medium\n");
	}
	print $fh ("!thumbsize $thumb\n")
	  if !$optcfg{"thumb"} && $thumb != DEFAULTS->{thumbsize};
	print $fh ("!page ${index_rows}x${index_columns}\n")
	  if !$optcfg{index_rows} && $index_rows != DEFAULTS->{indexrows}
	      || !$optcfg{index_columns} && $index_columns != DEFAULTS->{indexcols};
	print $fh ("!caption $caption\n")
	  if !$optcfg{"caption"} && $caption ne DEFAULTS->{caption};
    }
    print $fh ("\n# New entries added by Album $::VERSION, ".
	       localtime(time), "\n",
	       $newinfo,
	       "\n");
    close($fh);

    $new;
}

sub prepare_images {
    my $ddot = 0;
    my $tdot = 0;
    my $fmt = "[%" . length($filelist->tally) . "d]\n";
    my $msgfile;
    my $msg = sub {
	return unless $verbose > 1;

	if ( $verbose > 2 ) {
	    if ( $msgfile ) {
		print STDERR ("$msgfile: ");
		$msgfile = "";
	    }

	    print STDERR (@_ ? @_ : "OK\n");
	}

	unless ( @_ ) {
	    unless ( $msgfile ) {
		print STDERR ("OK\n");
		return;
	    }
	    print STDERR (".");
	    $tdot++;
	    if ( ++$ddot >= 50 ) {
		printf STDERR ($fmt, $tdot);
		$ddot = 0;
	    }
	    return;
	}

	printf STDERR ($fmt, $tdot) if $ddot;
	$ddot = 0;

	if ( $msgfile ) {
	    print STDERR ("$msgfile: ");
	    $msgfile = "";
	    $tdot++;
	}

	print STDERR (@_);
    };

    my $image;
    my $i_large;

    my $readimage = sub {
	my ($file) = (@_, $i_large);
	$image = new Image::Magick;
	my $t = $image->Read($file);
	warn("read($file): $t\n") if $t;
	#$image->Profile(name => "*", profile => undef);
    };

    my $resize = sub {
	 my ($n) = @_;
	 my ($origx, $origy) = $image->Get(qw(width height));
	 return unless $forcemedium || $origx > $n || $origy > $n;
	 my $ratio = $origx > $origy ? $origx / $n : $origy / $n;
	 my $t = $image->Resize(width => $origx/$ratio, height => $origy/$ratio);
	 warn("resize: $t\n") if $t;
    };

    foreach my $el ( $filelist->entries ) {
	$msg->(), next unless $el->type > 0;
	my $file = $el->dest_name;
	$msgfile = $file;
	$image = undef;

	# Check for directory names, e.g. f01/p01.jpg.
	my $dn = dirname($file);
	if ( $dn && $dn ne "." ) { # we have a dir name.
	    mkpath([d_index($dn), d_large($dn)], 1);
	    mkpath([d_medium($dn)], 1) if $medium;
	}

	$i_large = d_large($file);
	my $movie = $el->type == T_MPG;

	# Copy the file into place.
	if ( ! -s $i_large && $el->orig_name ) {
	    my $i_src = $el->orig_name;
	    my $time = $el->timestamp;

	    if ( $movie ) {

		# Need copy?
		my $copyit = !$linkthem
		  || (($el->rotation || $el->mirror) && $prog_mencoder);

		# Try to link.
		if ( !$copyit ) {
		    $msg->("link ");
		    if ( link($i_src, $i_large) == 1 ) {
			# Ok, done.
		    }
		    else {
			# Need copy.
			unlink($i_large); # just in case
			$msg->("[copy] ");
			$copyit = 1;
		    }
		}
		else {
		    $msg->("copy");
		}

		# Need copy?
		if ( $copyit ) {
		    if ( $prog_mencoder ) {
			$msg->("/rotate (be patient)") if $el->rotation;
			$msg->(" ");
			# Currently. movies have a bad ugly copy routine...
			copy_mpg($i_src, $i_large, $time,
				 $el->rotation, $el->mirror);
		    }
		    else {
			$msg->(" [no rotation]") if $el->rotation;
			$msg->(" ");
			copy($i_src, $i_large, $time);
		    }
		}
	    }
	    elsif ( $el->rotation || $el->mirror ) {
		$msg->("copy");
		$msg->("/rotate") if $el->rotation;
		$msg->("/mirror") if $el->mirror;
		$msg->(" ");

		# Use jpegtran to rotate jpg files.
		if ( ($el->file_ext || "") eq "jpg" && $prog_jpegtran ) {
		    my $cmd = "$prog_jpegtran -copy all -rotate " . $el->rotation . " ";
		    $cmd .= $el->mirror eq 'h' ? "-transpose " : "-transverse "
		      if $el->mirror;
		    $cmd .= "-outfile " . squote($i_large) .
		      " " . squote($i_src);
		    my $t = `$cmd 2>&1`;
		    $msg->($t) if $t;
		    utime($time, $time, $i_large);
		}
		# Otherwise, let Image::Magick handle it.
		else {
		    $readimage->($i_src);
		    $image->Rotate();
		    if ( $el->mirror ) {
			$image->Flip if $el->mirror eq 'h';
			$image->Flop if $el->mirror eq 'v';
		    }
		    my $t = $image->Write($i_large);
		    $msg->($t) if $t;
		    utime($time, $time, $i_large);
		}
	    }
	    elsif ( $linkthem ) {
		$msg->("link ");
		unless ( link($i_src, $i_large) == 1 ) {
		    unlink($i_large); # just in case
		    $msg->("[copy] ");
		    copy($i_src, $i_large, $time);
		}
	    }
	    else {
		$msg->("copy ");
		copy($i_src, $i_large, $time);
	    }
	    if ( $el->type == T_VOICE ) {
		$msg->("sound ");
		copy_voice($i_src, d_large($el->assoc_name),
			   $time);
	    }
	}
	if ( $movie ) {
	    $movie = $file;
	    $file = $el->assoc_name;
	    $i_large = d_large($file);
	    unless ( -s $i_large ) {
		$msg->("still ");
		$image = still($el);
	    }
	}

	my $i_medium = d_medium($file);
	my $i_small  = d_index($file);

	if ( $medium && ! -s $i_medium ) {
	    $readimage->() unless $image;
	    $msg->("medium ");
	    $resize->($medium);
	    my $t = $image->Write($i_medium);
	    $msg->($t) if $t;
	}
	$el->medium_size(-s $i_medium) if $medium && !$movie;

	if ( ! -s $i_small ) {
	    $readimage->() unless $image;
	    $msg->("thumbnail ");
	    $resize->($thumb);
	    my $t = $image->Write($i_small);
	    $msg->($t) if $t;
	}

	$msg->(); 		# flush

    }
    printf STDERR ($fmt, $tdot) if $ddot && $tdot;
}

################ Formats ################

my %format_for;

# <<HereDoc helper to retain a nice program layout.
sub heredoc($$) {
    my ($doc, $indent) = @_;
    $indent = " " x $indent;
    my $res = "";
    foreach ( split(/\n/, $doc) ) {
	my $line = detab($_);
	$line =~ s/^$indent//;
	$res .= $line . "\n";
    }
    $res;
}

sub init_formats {
    my $did = 0;
    my $load = sub {
	my ($req, $data) = @_;
	my $fmt = d_fmt($req);
	if ( -s $fmt ) {
	    local($/);
	    open (my $fh, $fmt) || die("$fmt: $!\n");
	    $data = <$fh>;
	    close($fh);
	}
	elsif ( $externalize_formats ) {
	    unless ( $did ) {
		my $fdir = d_fmt("");
		$fdir =~ s/\/+$//;
		unless ( -d $fdir ) {
		    print STDERR ("mkdir $fdir\n");
		    mkdir(d_fmt(""));
		}
	    }
	    print STDERR ("Creating formats: ") if $verbose > 1 && !$did++;
	    print STDERR ("$req ") if $verbose > 1;
	    open (my $fh, '>', $fmt) || die("$fmt: $!\n");
	    print {$fh} $data;
	    close($fh);
	}
	$data =~ s/^([ \t]+)/detab($1)/gem;
	$data;
    };

    # Format for main index page.
    #
    # Variables:
    #
    #  $link
    #  $title
    #  $index
    #  $version

    $format_for{main} = $load->("main.fmt", heredoc(<<'    EOD', 4));
    <?xml version="1.0" encoding="iso-8859-15"?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <!-- ALBUM-FMT-VERSION: 2.0 -->
    <html>
      <head>
        <title>$title</title>
        $css
	<script type='text/javascript'>
	window.onclick = function() {
	  document.location = "$link";
	}
        </script>
      </head>
      <body>
	<p class='indextitle'><a href="$link">
                              $title</a></p>
	<p class='ftr'>Created with Album $version</p>
      </body>
    </html>
    EOD

    # Format for index pages (mostly).
    #
    # Variables:
    #
    #  $title
    #  $ltop
    #  $rtop
    #  $vbuttons / $hbuttons
    #  $jscript
    #  $contents

    $format_for{index} = $load->("index.fmt", heredoc(<<'    EOD', 4));
    <?xml version="1.0" encoding="iso-8859-15"?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <!-- ALBUM-FMT-VERSION: 2.0 -->
    <html>
      <head>
	<title>$title</title>
        $css
	$jscript
      </head>
      <body>
	<table>
	  <tr>
	    <td></td>
	    <td class='topleft'>
	      <p class='hdl'>
		$ltop
	      </p>
	    </td>
	    <td class='topright'>
	      <p class='hdr'>
		$rtop
	      </p>
	    </td>
	  </tr>
	  <tr>
	    <td class='vbuttons'>
	      $vbuttons
	    </td>
	    <td class='vimage' colspan='2'>
	      $contents
	    </td>
	  </tr>
	</table>
      </body>
    </html>
    EOD

    # Format for image pages (mostly).
    #
    # Variables:
    #
    #  $title
    #  $ltop
    #  $rtop
    #  $vbuttons / $hbuttons
    #  $jscript
    #  $image
    #  $lbot
    #  $rbot

    $format_for{image} = $load->("image.fmt", heredoc(<<'    EOD', 4));
    <?xml version="1.0" encoding="iso-8859-15"?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <!-- ALBUM-FMT-VERSION: 2.0 -->
    <html>
      <head>
	<title>$title</title>
        $css
	$jscript
      </head>
      <body>
	<table>
	  <tr>
	    <td></td>
	    <td class='topleft'>
	      <p class='hdl'>
		$ltop
	      </p>
	    </td>
	    <td class='topright'>
	      <p class='hdr'>
		$rtop
	      </p>
	    </td>
	  </tr>
	  <tr>
	    <td class='vbuttons'>
	      $vbuttons
	    </td>
	    <td class='image' colspan='2'>
	      $image
	    </td>
	  </tr>
	  <tr>
	    <td></td>
	    <td class='botleft'>
	      <p class='ftl'>
		$lbot
	      </p>
	    </td>
	    <td class='botright'>
	      <p class='ftr'>
		$rbot
	      </p>
	    </td>
	  </tr>
	</table>
      </body>
    </html>
    EOD

    $format_for{large}  = $load->("large.fmt",  $format_for{image});
    $format_for{medium} = $load->("medium.fmt", $format_for{image});

    # Format for journal pages (mostly).
    #
    # Variables:
    #
    #  $title
    #  $tag
    #  $vbuttons / $hbuttons
    #  $journal
    #  $jscript

    $format_for{journal} = $load->("journal.fmt", heredoc(<<'    EOD', 4));
    <?xml version="1.0" encoding="iso-8859-15"?>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <!-- ALBUM-FMT-VERSION: 2.0 -->
    <html>
      <head>
	<title>$title</title>
        $css
	$jscript
      </head>
      <body>
	<table class='outer'>
	  <tr class='grey'>
	    <td>
	      <p class='hdl'>
		$tag
	      </p>
	    </td>
	    <td class='buttons'>
	      $hbuttons
	    </td>
	  </tr>
	  $journal
	  <tr class='grey'>
	    <td></td>
	    <td class='buttons'>
	      $hbuttons
	    </td>
	  </tr>
	</table>
      </body>
    </html>
    EOD

    print STDERR ("\n") if $did;
}

sub process_fmt {
    my ($fmt, %map) = @_;
    # THIS DOES ONLY ONE SUBST PER LINE
    $fmt =~ s/^(.*?)\$(\w+)\b/$1.indent($map{$2}, length($1))/gme;
    $fmt;
}

################ Style Sheets ################

my %css_for;

sub init_stylesheets {

    my $css_fontfam = "font-family: Verdana, Arial, Helvetica";
    my $WHITE = "#FFFFFF";
    my $BLACK = "#000000";
    my $RED   = "#FF0000";
    my $LGREY = "#E0E0E0";
    my $MGREY = "#D0D0D0";
    my $DGREY = "#C0C0C0";
    my $DDGREY = "#B0B0B0";
    my $BLUE  = "#0000FF";
    # Grey variants for index table borders.
    my $GR245 = "#F5F5F5";
    my $GR232 = "#E8E8E8";
    my $GR124 = "#7C7C7C";
    my $GR114 = "#727272";

    my $helper = $thumb + 4;

    my $did = 0;
    my $load = sub {
	my ($req, $data) = @_;
	my $css = d_css($req.".css");
	if ( -r $css ) {
	    my $major = $css_major;
	    my $minor = $css_minor;
	    my $orig;
	    if ( open(my $orig, "<", d_fmt("$req.fmt")) ) {
		my $line = <$orig>;
		close($orig);
		if ( $line =~ m;/\*\s*album-fmt-version:\s*(\d+)\.(\d+)\s*\*/;i ) {
		    ($major, $minor) = ($1, $2);
		}
	    }
	    # Check stylesheet compatibility.
	    open($orig, "<", $css);
	    my $line = <$orig>;
	    close($orig);
	    if ( $line =~ m;/\*\s*album-css-version:\s*(\d+)\.(\d+)\s*\*/;i ) {
		if ( $1 == $major ) {
		    return "";
		}
	    }
	    print STDERR "\n" if $did;
	    die(heredoc(<<"            EOD", 8));
	    *************************************************************************
	    Existing style sheet $req.css is not compatible with this version.
	    It has probably been created by an older version of this program, or it
	    has been modified manually.

	    If you did not change any style sheets, just remove the css directory and
	    try again.

	    If you did modify the style sheets move them away to a backup location,
	    run the program with '--extcss', and apply your changes to the new style
            sheets.
	    *************************************************************************
            EOD
	}
	elsif ( $externalize_css ) {
	    unless ( $did ) {
		my $fdir = d_css("");
		$fdir =~ s/\/+$//;
		unless ( -d $fdir ) {
		    print STDERR ("mkdir $fdir\n");
		    mkdir(d_css(""));
		}
	    }
	    print STDERR ("Creating stylesheets: ") if $verbose > 1 && !$did++;
	    print STDERR ("$req ") if $verbose > 1;
	    open (my $fh, '>', $css) || die("$css: $!\n");
	    print { $fh } $data;
	    close($fh);
	}
	$data =~ s/^([ \t]+)/detab($1)/gem;
	$data;
    };

    my $css_for_common = heredoc(<<"    EOD", 0);

    body {
	$css_fontfam;
	font-size:  80%;
	text: $BLACK;
    }

    a:link {
	color: $BLACK; text-decoration: none;
    }
    a:visited {
	color: $BLACK; text-decoration: none;
    }
    a:active {
	color: $RED; text-decoration: none;
    }

    img.image {
	border: 2px solid $BLACK;
    }

    img.button {
	border: 0;
	vertical-align: top;
    }

    table.vb {
	border: 0;
	border-spacing: 0 0;
    }
    table.vb td {
	padding: 0 0 0 0;
    }
    table.hb {
	border: 0;
	border-spacing: 0 0;
    }
    table.hb td {
	padding: 0 0 0 0;
    }
    EOD

    my $css_for_ipage = heredoc(<<"    EOD", 0);
    $css_for_common
    body {
	background: $DGREY;
    }
    td {
	font-size:  80%;
    }
    p.hdl, p.hdr {
	font-size: 140%; font-weight: bold;
	margin-top: 0; margin-bottom: 0;
    }
    p.ftl, p.ftr {
	font-size:  80%;
	margin-top: 0; margin-bottom: 0;
    }
    td.topleft {
	text-align: left;
	vertical-align: top;
    }
    td.topright {
	text-align: right;
	vertical-align: top;
    }
    td.image {
	text-align: center;
	vertical-align: top;
    }
    td.botleft {
	text-align: left;
	vertical-align: top;
    }
    td.botright {
	text-align: right;
	vertical-align: top;
    }
    td.vbuttons {
	vertical-align: top;
    }
    EOD

    $css_for{index} = $load->("index", heredoc(<<"    EOD", 4));
    /* ALBUM-CSS-VERSION: ${css_major}.${css_minor} */
    $css_for_ipage
    a.info {
	position: relative; z-index: 24; background-color: $LGREY;
	color: $BLACK; text-decoration:none;
    }
    a.info:hover {
	z-index: 25; background-color: $LGREY;
    }
    a.info span {
	display: none;
    }
    a.info:hover span {
	display: block;
	position: absolute; top: 2em; left: 2em; width: 25em;
	border: 0px; background-color: $MGREY; color: $BLACK;
	text-align: center;
    }
    table.outer {
	background: $MGREY;
	border-collapse: separate;
	border-width: 2px;           /* border=2 */
	border-style: solid;
	border-color: $GR232 $GR114 $GR114 $GR232;
	border-spacing: 3px;        /* cellspacing = 3 */
    }
    table.outer tr {
	background: $LGREY;
    }
    table.outer td {
	border-width: 1px;
	border-style: solid;
	border-color: $GR124 $GR245 $GR245 $GR124;
    }
    table.inner {
	/* need a width otherwise we cannot center it */
	width: ${helper}px;
	border: outset 0px;
    }
    table.inner td {
	border: inset 0px;
        padding: 0 0 0 0;
    }
    p.hdr {
	font-size: 140%; font-weight: bold;
	margin-top: 0; margin-bottom: 0;
    }
    p.hdr a:link {
	color: $BLACK; text-decoration: underline;
    }
    p.hdr a:visited {
	color: $BLACK; text-decoration: underline;
    }
    p.hdr a:hover {
	color: $RED; text-decoration: underline;
    }
    td.vimage {
	vertical-align: top;
    }
    td.oimg {
	text-align: center;
	vertical-align: bottom;
    }
    td.iimg {
	text-align: center;
    }
    td.itxt {
	text-align: center;
    }
    img.thumb {
	border: 0;
    }
    EOD

    my $css_for_image = heredoc(<<"    EOD", 4);
    /* ALBUM-CSS-VERSION: ${css_major}.${css_minor} */
    $css_for_ipage
    a.info {
	position: relative; z-index: 24; background-color: $DGREY;
	color:$BLACK; text-decoration:none;
    }
    a.info:hover {
	z-index: 25; background-color: $DGREY;
    }
    a.info span {
	display: none;
    }
    a.info:hover span {
	display: block;
	position: absolute; top:2em; left: 2em; width: 15em;
	border: 0px; background-color: $MGREY; color: $BLACK;
	text-align: center;
    }
    EOD

    $css_for{large}   = $load->("large",   $css_for_image);
    $css_for{medium}  = $load->("medium",  $css_for_image);

    $css_for{journal} = $load->("journal", heredoc(<<"    EOD", 4));
    /* ALBUM-CSS-VERSION: ${css_major}.${css_minor} */
    $css_for_common
    body {
	font-size: 100%;
	background: $WHITE;
    }
    td {
	font-size:  100%;
    }
    p.hdl {
	font-size: 140%; font-weight: bold;
	margin-left: 0.1in; margin-top: 0.1in; margin-bottom: 0.1in;
    }

    table.outer {
	width: 600px;
	border-spacing: 10px;
    }
    tr.grey {
	background: $DGREY;
    }
    table.outer td.twocol {
	vertical-align: top;
	text-align: left;
    }
    table.outer td.jl {
	vertical-align: top;
	text-align: left;
    }
    table.outer td.jr {
	width: ${thumb}px;
	vertical-align: top;
        text-align: center;
        background: $LGREY;
    }
    table.outer td.buttons {
	vertical-align: middle;
	text-align: right;
	padding-right: 0.1in;
    }
    EOD

    $css_for{main} = $load->("main", heredoc(<<"    EOD", 4));
    /* ALBUM-CSS-VERSION: ${css_major}.${css_minor} */
    body {
        $css_fontfam;
        font-size:  80%;
        background: $LGREY;
        background-image: url("icons/bg.jpg");
        background-repeat: no-repeat;
        background-position: 10% 60%;
    }
    p.ftr {
        padding-left: 10%;
        padding-top: 40%;
        font-size:  80%;
        text-align: left;
        color: $DDGREY;
    }
    p.indextitle {
        padding-left: 10%;
        font-size: 500%;
        font-weight: bold;
        color: $WHITE;
    }
    p.indextitle a {
        text-decoration: none;
        color: $WHITE;
    }
    EOD
}

sub css_for {
    my ($type) = shift(@_);
    defined(my $css = $css_for{$type}) or die("PROGRAM ERROR: css_for($type)");
    return qq{<link rel="stylesheet" href="}.d_up(d_css($type.".css")).qq{">}
      unless $css;
    qq{<style type=\"text/css\">\n} . $css . qq{</style>};
}

################ Helpers for Image/Index/Journal pages ################

sub jscript {
    my (%nav) = @_;
    my $next = $nav{next};
    my $prev = $nav{prev};
    my $up   = $nav{up};
    my $down = $nav{down};
    my $idx  = $nav{idx};
    my $jnl  = $nav{jnl};
    my $js = heredoc(<<"    EOD", 4);
    <script type='text/javascript'>
    function handleKey(e) {
      var key;
      if ( e == null ) { // IE
	key = event.keyCode
      }
      else { // Mozilla
	if ( e.altKey || e.ctrlKey ) {
	  return true
	}
	key = e.which
      }
      switch(key) {
    EOD

    $js .= "    case   8: window.location = '$prev'; break // Backspace\n" if $prev;
    $js .= "    case  32: window.location = '$next'; break // Space\n"     if $next;
    $js .= "    case  13: window.location = '$down'; break // Enter\n"     if $down;
    $js .= "    case 117: window.location = '$up'; break // 'u'\n"         if $up;
    $js .= "    case 100: window.location = '$idx'; break // 'd'\n"        if $idx;
    $js .= "    case 106: window.location = '$jnl'; break // 'j'\n"        if $jnl;

    $js .= heredoc(<<"    EOD", 4);
       default:
      }
      return false
    }
    
    document.onkeypress = handleKey
    </script>
    EOD
    $js;
}

sub button($$;$$) {
    my ($tag, $link, $level, $active) = @_;
    my $Tag = ucfirst($tag);
    local $fjoin = \&hjoin;

    $level  = 0 unless defined $level;
    $active = 1 unless defined $active;
    $tag .= "-gr" unless $active;
    my @path;
    push(@path, ("..") x $level) if $level;
    push(@path, d_icons("$tag.png"));
    my $path = $fjoin->(@path);
    my $b = img($path, class => "button",
		alt => "[$Tag]");
    $active ? "<a class='info' href='$link' title='$Tag'>$b</a>" : $b;
}

sub hbuttons {
    my (@b) = @_;

    # When using a <table>, it seems to be impossible to get it
    # aligned properly on the journal pages. Apparently,
    # <td align=right> semantics cannot be implemented using CSS.

    "<div class='hb'>".
    join("", map { $_ } @b).
    "</div>";
}

sub vbuttons {
    my (@b) = @_;
    "<table class='vb'>".
    join("", map { "<tr><td>$_</td></tr>"} @b).
    "</table>";
}

sub ixname($;$) {
    my ($n, $rel) = @_;
    my $f = sprintf("index%04d.html", $n + 1);
    return $f if $rel;
    d_index($f);
}

# To aid XHTML compliancy.
sub br { "<br />" }

# Pseudo-smart approach to creating paired single/double quotes.
# Note that the (s-|s\s|t\s) case is specific to the dutch language,
# but probably won't harm other languages...
# Yes, you'll get stupid results with input like rock'n'roll.

sub fixquotes($) {
    my ($t) = @_;

    # HTML::Entities will already have turned " into &quot; -- undo.
    $t =~ s/\&quot;/"/g;
    while ( $t =~ /^([^"]*)"([^"]+)"(.*)/s ) {
	$t = $1 . "&ldquo;" . $2 . "&rdquo;" . $3;
    }
    $t =~ s/"/&quot;/g;

    # HTML::Entities will already have turned ' into &#39; -- undo.
    $t =~ s/\&#39;/'/g;
    while ( $t =~ /^(.*?)'(s-|s\s|t\s)(.*)/s ) {
	$t = $1 . "&apos;" . $2 . $3;
    }
    while ( $t =~ /^([^']*)'([^']+)'(.*)/s ) {
	$t = $1 . "&lsquo;" . $2 . "&rsquo;" . $3;
    }
    $t;
}

# Escape sensitive characters in HTML.
# Two variants: one using HTML::Entities, the other a dumber stub.
# If HTML::Entities is available, it will be used.

sub html($) {
    eval {
	require HTML::Entities;

	if ( !$encoding or $encoding =~ /^(iso-?8859-?15|latin-?9)$/i ) {
	    # Apply Latin-9 instead of Latin-1.
	    no warnings 'once';
	    for ( \%HTML::Entities::char2entity ) {
		$_->{chr(0204)} = '&euro;';
		$_->{chr(0246)} = '&Scaron;';
		$_->{chr(0250)} = '&scaron;';
		$_->{chr(0264)} = '&Zcaron;';
		$_->{chr(0270)} = '&zcaron;';
		$_->{chr(0274)} = '&OE;';
		$_->{chr(0275)} = '&oe;';
		$_->{chr(0276)} = '&Yuml;';
	    }
	}
	no warnings 'redefine';
	*html = sub($) {
	    my ($t) = @_;
	    return '' unless $t;
	    $t = HTML::Entities::encode($t);
	    fixquotes($t);
	};
    };
    if ( $@ ) {
	if ( $encoding ) {
	    warn("WARNING: Module HTML::Entities not found.\n".
		 "Encoding of the HTML files may be incorrect!\n");
	}
	no warnings 'redefine';
	*html = sub($) {
	    my ($t) = @_;
	    return '' unless $t;
	    $t =~ s/&/&amp;/g;
	    $t =~ s/</&lt;/g;
	    $t =~ s/>/&gt;/g;
	    fixquotes($t);
	};
    }
    goto &html;
}

sub htmln($) {
    # Escape HTML sensitive characters, and turn newlines into <br>.
    my $t = html(shift);
    return '' unless $t;
    $t =~ s/\n+/$br/go;
    $t;
}

sub indent($$) {
    # Shift contents to the right so it fits pretty.
    my ($t, $n) = @_;
    $n = " " x $n;
    return $n unless $t;
    $t = detab($t);
    $t =~ s/\n+$//;
    $t =~ s/\n/\n$n/g;
    $t;
}

sub img($%) {
    my ($file, %atts) = @_;
    my $ret = "<img src='" . $file . "'";
    foreach ( sort(keys(%atts)) ) {
	$ret .= " $_='" . $atts{$_} . "'";
    }
    $ret . "/>";
}

#### Size helpers.

sub bytes($) {
    my $t = shift;
    return $t . "b" if $t < 10*1024;
    return ($t >> 10) . "kb" if $t < 10*1024*1024;
    ($t >> 20) . "Mb";
}

sub size_info($;$) {
    my ($el, $med) = @_;
    return unless $el->width;

    my $ret = "";
    $ret .= $el->width . "x" . $el->height if $el->width;
    for ( $med ? $el->medium_size : $el->file_size ) {
	next unless $_;
	$ret .= "," if $ret;
	$ret .= bytes($_);
    }
    $ret;
}

#### EXIF helpers.

sub restyle_exif($) {
    my ($el) = @_;
    my $ret = "";
    my $v;

    my $app = sub {
	$ret .= "<tr><td>".htmln($_[0])."</td>".
	            "<td>".htmln($_[1])."</td></tr>\n";
    };

    $app->("Date", $v) if $v = $el->DateTime;
    my $t = $el->ExposureTime || 0;
    if ( $t && $t <= 0.5 ) {
	$t = "1/".int(0.5 + 1/$t)."s";
    }
    $app->("Exposure",
	   join(" ", $el->ExposureMode || "",
		$el->ExposureProgram || "", $t));
    $app->("Aperture", sprintf("%.1f", $v))
      if $v = $el->FNumber;
    if ( $v = $el->FocalLength ) {
	if ( $el->Model eq "DSC-V1" ) {
	    $v .= sprintf("mm  (%.1fmm equiv.)", $v*4.857);
	}
	else {
	    $v .= "mm";
	}
	$app->("Focal length", $v);
    }
    $app->("ISO", $v) if $v = $el->ISOSpeedRatings;
    $app->("Flash", $v)
      if ($v = $el->Flash) && $v ne "Flash did not fire";
    $app->("Metering", $v) if $v = $el->MeteringMode;
    $app->("Scene", $v) if $v = $el->SceneCaptureType;
    $app->("Camera",
	   join(" ", $v, $el->Model))
      if $v = $el->Make;
}

#### Caption helpers.

sub f_caption($) {
    my ($el) = @_;
    my $s = htmln($el->type == T_REF ? $el->orig_name : $el->dest_name);
    if ( $el->Make ) {
	$s = "&nbsp;$s<a href='#' class='info'>&nbsp;<span>".
	  "<table border='1' width='100%'>\n".
	    restyle_exif($el) . "</table>\n".
	      "</span></a>";
    }
    $s;
}

sub s_caption($) {
    my ($el) = @_;
    size_info($el, $medium);
}

sub t_caption($) {
    my ($el) = @_;
    $el->tag  ? htmln($el->tag) : "";
}

sub c_caption($) {
    my ($el) = @_;
    my $t = $el->description || "";
    $t =~ s/\n.*//;
    htmln($t);
}

#### Misc.

sub update_if_needed($$) {
    my ($fname, $new) = @_;

    # Do not overwrite unless modified.
    if ( -s $fname && -s _ == length($new) ) {
	local($/);
	my $hh = do { local *F; *F };
	my $old;
	open($hh, "<", $fname) && ($old = <$hh>) && close($hh);
	if ( $old eq $new ) {
	    return 0;
	}
    }

    my $fh = do { local *F; *F };
    open($fh, ">", $fname)
      or die("$fname (create): $!\n");
    print $fh $new;
    close($fh);
    1;
}

sub uptodate($$) {
    my ($type, $mod) = @_;
    if ( $mod ) {
	print STDERR ("(Needed to write ", $mod,
		      " $type page", $mod == 1 ? "" : "s", ")\n");
    }
    else {
	print STDERR ("(No $type pages needed updating)\n");
    }
}

################ Image Pages ################

sub write_image_pages {
    print STDERR ("Creating ", $num_entries, " image page",
		  $num_entries == 1 ? "" : "s", "\n") if $verbose > 1;
    my $mod = 0;

    for my $el ( $filelist->entries ) {
	write_image_page($el, "large") && $mod++;
	write_image_page($el, "medium") && $mod++ if $medium;
    }
    uptodate("image", $mod) if $verbose > 1;
}

sub write_image_page {
    my ($el, $dir) = @_;
    if ( $el->type <= T_PSEUDO ) {
	warn("PSEUDO: ", Dumper($el)) unless $el->type == T_REF;
	return;
    }

    # Local scope...
    my $orig_dd = $dest_dir;
    local $dest_dir = ".";
    local $fjoin = \&hjoin;

    my $i = $el->seq - 1;
    my $file = $el->dest_name;
    my $rf = $file;

    # Try movie.
    my $movie = $el->type == T_MPG;
    if ( $movie ) {
	$file = $el->assoc_name;
    }

    my $tt = "$album_title: Image " . ($i+1);
    $tt .= " of " . $num_entries if $num_entries > 1;
    $tt = htmln($tt);
    my $it = htmln($el->description) || "";

    my $next = ($el->next || $num_entries+1) - 1;
    my $prev = ($el->prev || 0) - 1;
    my %nav = (next => $next < $num_entries ? $htmllist[$next] : "",
	       prev => $prev >= 0 ? $htmllist[$prev] : "",
	       idx  => d_up(ixname(int($i/$entries_per_page))),
	       up   => d_up(ixname(int($i/$entries_per_page))));

    my @b = (
	     ($dir eq "large" && $medium) ?
	     button("medium", d_up(d_medium($htmllist[$i])),           1, 1) :
	     button("index",  d_up(ixname(int($i/$entries_per_page))), 1, 1),
	     button("first",  $htmllist[0],                            1, $i > 0),
	     button("prev",   $htmllist[$prev] || "",                  1, $prev >= 0),
	     button("next",   $htmllist[$next] || "",                  1, $next < $num_entries),
	     button("last",   $htmllist[-1],                           1, $i < $num_entries-1));

    if ( $journal && exists $jnltags{$el->tag} ) {
	my $page = d_up(d_journal("jnl" . $jnltags{$el->tag} . ".html#img".sprintf("%04d", $i+1)));
	push(@b, button("journal", $page, 1, 1));
	$nav{jnl} = $page;
    }
    if ( $el->type == T_VOICE ) {
	my $sound = $el->assoc_name;
	push(@b, button("sound", d_up(d_large($sound)), 1, 1));
    }

    my $imglink;
    if ( $dir eq "medium" ) {
	if ( $mediumonly ) {
	    $imglink = img($file, alt => "[Image]", class => "image");
	}
	elsif ( $movie ) {
	    $imglink = "<a href='" . d_up(d_large($el->dest_name)) . "'>" .
	      img($file, alt => "[Movie]", class => "image") .
		"</a>";
	    $nav{down} = d_up(d_large($el->dest_name));
	}
	else {
	    $imglink = "<a href='" . d_up(d_large($htmllist[$i])) . "'>" .
	      img($file, alt => "[Image]", class => "image") .
		"</a>";
	    $nav{down} = d_up(d_large($htmllist[$i]));
	}
    }
    else {
	if ( $movie ) {
	    $imglink = "<a href='" . $el->dest_name . "'>" .
	      img($file, alt => "[Movie]", class => "image") .
		"</a>";
	}
	else {
	    $imglink = img($file, alt => "[Image]", class => "image");
	}
	$nav{up} = d_up(d_medium($htmllist[$i]));
    }

    my $auxright = htmln($el->dest_name);
    my $s = size_info($el);
    $auxright .= " ($s)" if $s;
    $auxright .= "&nbsp;&nbsp;&nbsp;$creator" if $creator;
    my $auxleft  = htmln($el->tag || "");

    my $it2 = $it;
    if ( $el->Make ) {		# EXIF info
	$it2 = "<a href='#' class='info'>" . ($it || "&nbsp;") .
	  "<span>" .
	    "<table border='1' width='100%'>\n" .
	      restyle_exif($el) . "</table>\n" .
		"</span></a>";
    }
    my $tt2 = $tt;

    if ( $dir eq "medium" && $el->annotation ) {
	my @a = UNIVERSAL::isa($el->annotation, "ARRAY")
	  ? @{$el->annotation} : ($el->annotation);
	my $t = "";
	foreach ( reverse(@{$el->annotation}) ) {
	    next unless $_;
	    my $x = $_;		# copy
	    $x = html($x) unless $x =~ /^</;
	    $t .= "<p>\n" if $t;
	    $t .= $x;
	}
	$tt2 = "<a href='#' class='info'>" . $tt .
	  "<span>" .
	    "<table border='1' width='100%'>\n" .
	      "<tr><td>$t</td></tr>" .
		"</table>\n" .
		  "</span></a>" if $t;
    }

    # Restore local scope.
    $fjoin = \&fjoin;
    $dest_dir = $orig_dd;

    update_if_needed(d_dest($dir, $htmllist[$i]),
		     process_fmt($format_for{$dir},
				 title	  => $it || $tt,
				 css      => css_for($dir),
				 dir	  => $dir,
				 ltop	  => $it2,
				 rtop	  => $tt2,
				 hbuttons => hbuttons(@b),
				 vbuttons => vbuttons(@b),
				 jscript  => jscript(%nav),
				 image	  => $imglink,
				 lbot	  => $auxleft,
				 rbot	  => $auxright,
				));
}

################ Index Pages ################

sub write_index_pages {
    print STDERR ("Creating ", $num_indexes, " index page",
		  $num_indexes == 1 ? "" : "s", "\n") if $verbose > 1;
    my $mod = 0;
    for my $i ( 0 .. $num_indexes-1 ) {
	write_index_page($i) && $mod++;
    }
    uptodate("index", $mod) if $verbose > 1;

    # Cleanup excess indices.
    for (my $i = $num_indexes ; ; $i++ ) {
	unlink(d_dest("index$i.html")) or last;
    }
}

sub write_index_page {
    my ($x) = @_;

    my $tt = $album_title.": Index"; # left title
    my $t = "";			# right (index select)
    my @b;			# buttons
    my %nav;

    # Local scope...
    my $orig_dd = $dest_dir;
    local $dest_dir = ".";
    local $fjoin = \&hjoin;

    # Construct buttons and index selector.
    if ( $num_indexes > 1 ) {
	$nav{next} = ixname($x+1, 1) if $x < $num_indexes-1;
	$nav{prev} = ixname($x-1, 1) if $x > 0;

	if ( $lib_common ne "" ) {
	    $nav{up}   = join("/","..",$lib_common,"index.html");
	}
	elsif ( $home_link ) {
	    $nav{up}   = join("/","..",$home_link);
	}

	push(@b, button("up", $nav{up},  1, 1)) if $nav{up};
	push(@b,
	     button("first", ixname(0, 1),              1, $x > 0             ),
	     button("prev",  ixname($x-1, 1),           1, $x > 0             ),
	     button("next",  ixname($x+1, 1),           1, $x < $num_indexes-1),
	     button("last",  ixname($num_indexes-1, 1), 1, $x < $num_indexes-1));
	$tt .= " " . ($x+1) . " of $num_indexes";
	my @ixlist = ( 0..$num_indexes-1 );
	if ( @ixlist > IXLIST ) {
	    @ixlist = ( $x );
	    while ( @ixlist < IXLIST ) {
		push(@ixlist, $ixlist[-1]+1)
		  if $ixlist[-1]+1 < $num_indexes;
		unshift(@ixlist, $ixlist[0]-1)
		  if @ixlist < IXLIST && $ixlist[0] > 0;
	    }
	}
	$t .= "...\n" if $ixlist[0];
	foreach ( @ixlist ) {
	    if ( $_ == $x ) {
		$t .= ($x+1) . "\n";
	    }
	    else {
		my $el = $filelist->byseq(($_ * $index_rows * $index_columns) + 1);
		$t .= "<a";
		if ( my $tag = $el->tag ) {
		    $t .= " title=\"$tag\"";
		}
		$t .= " href='" . ixname($_, 1) . "'>" . ($_+1) . "</a>\n";
	    }
	}
	$t .= "...\n" if $ixlist[-1] < $num_indexes-1;
    }
    elsif ( $lib_common ) {
	push(@b, button("up", join("/","..",$lib_common,"index.html"),  1, 1));
	$nav{up} = join("/","..",$lib_common,"index.html");
    }

    my $first_in_row = $x * $entries_per_page;

    if ( $journal && exists $jnltags{$filelist->byseq($first_in_row+1)->tag} ) {
	my $page = d_up(d_journal("jnl".$jnltags{$filelist->byseq($first_in_row+1)->tag} .
	  ".html#img" . sprintf("%04d", $first_in_row+1)));
	push(@b, button("journal", $page, 1, 1));
	$nav{jnl} = $page;
    }

    # Construct the actual index part.
    my $cc = "<table class='outer'>\n";

    for ( my $i = 0; $i < $index_rows; $i++, $first_in_row += $index_columns ) {
	if ( $first_in_row < $num_entries ) {
	    $cc .= "  <tr>\n";
	    for ( my $j = 0; $j < $index_columns; $j++ ) {
		my $this = $first_in_row + $j;
		if ( $this < $num_entries ) {
		    my $el = $filelist->byseq($this+1);
		    my $file = $el->dest_name;
		    my $img;
		    my $base;
		    my $target = "";
		    if ( $el->type == T_REF ) {
			$img = d_up($el->assoc_name);
			$base = d_up($el->dest_name);
			$target = " target=\"_blank\"";
		    }
		    else {
			$img = $el->type == T_MPG ? $el->assoc_name : $file;
			# $img = d_up(d_thumbnails($img));
			$base = d_up($medium ? d_medium($htmllist[$this]) : d_large($htmllist[$this]));
		    }

		    $cc .= heredoc(<<"                    EOD", 16);
		    <td class='oimg'>
		      <table class='inner'>
			<tr class='iimg'>
			  <td class='iimg'>
			    <a href='$base'$target>@{[img($img, alt => "[$img]", class => "thumb")]}</a>
			  </td>
			</tr>
			<tr class='itxt'>
			  <td class='itxt'>
			    <p class='ft'>@{[join($br, map { $capfun{$_}->($el) } split(//, $caption))]}</p>
			  </td>
			</tr>
		      </table>
		    </td>
                    EOD
		}
		else {
		    $cc .= "    <td width='$thumb'></td>\n";
		}
	    }
	    $cc .= "  </tr>\n";
	}
    }
    $cc .= "</table>\n";

    # Restore local scope.
    $fjoin = \&fjoin;
    $dest_dir = $orig_dd;

    update_if_needed(ixname($x),
		     process_fmt($format_for{index},
				 title    => $tt,
				 css      => css_for("index"),
				 ltop     => $tt,
				 rtop     => $t,
				 hbuttons => hbuttons(@b),
				 vbuttons => vbuttons(@b),
				 jscript  => jscript(%nav),
				 contents => $cc,
				));
}

################ Journal Pages ################

sub write_journal_pages {
    return unless $journal;
    print STDERR ("Creating ", $journal, " journal page",
		  $journal == 1 ? "" : "s", "\n") if $verbose > 1;
    mkpath([d_journal()], $verbose > 1);
    my $mod = write_journal();
    uptodate("journal", $mod) if $verbose > 1;
}

sub write_journal {
    my $jname = sub { sprintf("jnl%04d.html", shift) };

    my @ann;
    my $seq = 1;
    my $x = 0;
    my $tag;

    # Local scope...
    my $orig_dd = $dest_dir;
    local $dest_dir = ".";
    local $fjoin = \&hjoin;

    my $flush = sub {
	my $jnl = "";
	my $ix = int($seq / ($index_rows * $index_columns));
	foreach my $e ( @ann ) {
	    my $t = $e->annotation;
	    $t = (UNIVERSAL::isa($t, "ARRAY") ? $t->[0] : $t) || "";
	    $t = html($t) unless $t =~ /^</i;
	    if ( $e->type == T_ANN ) {
		$jnl .= "<tr>\n".
			"  <td class='twocol' colspan='2'>\n".
			"    " . indent($t, 4) . "\n".
			"  </td>\n".
			"</tr>\n";
		next;
	    }

	    # We cannot use $el->seq, since that's the info.dat order
	    # which includes the skipped entries.
	    my $dst = ($e->type == T_REF) ? $e->assoc_name :
	      d_index($e->type == T_MPG ? $e->assoc_name : $e->dest_name);
	    my $img = "<a name='" . sprintf("img%04d", $seq) . "' " .
	              ($e->type == T_REF ? " target=\"_blank\"" : "").
		      "href='" .
		      d_up(($e->type == T_REF ? $e->dest_name : d_medium(sprintf("img%04d.html", $seq)))) .
#		      "' border='0" .
		      "'>" .
		      "<img src='" . d_up($dst) . "'/></a>";

	    $jnl .= "<tr>\n".
	            "  <td class='jl'>\n".
		    "    " . indent($t || "&nbsp;", 4) . "\n".
		    "  </td>\n".
		    "  <td class='jr'>\n".
		    "    " . indent($img, 4) . "\n".
		    "  </td>\n".
		    "</tr>\n";
	    $seq++;
	}
	my @b = ( button("first", $jname->(1),         1, $x > 0         ),
		  button("prev",  $jname->($x),        1, $x > 0         ),
		  button("next",  $jname->($x+2),      1, $x < $journal-1),
		  button("last",  $jname->($journal),  1, $x < $journal-1),
		  button("index", d_up(ixname($ix)),   1, 1             ),
	     );
	my %nav = ( up  => d_up(ixname($ix)),
		    idx => d_up(ixname($ix)) );
	$nav{prev} = $jname->($x) if $x > 0;
	$nav{next} = $jname->($x+2) if $x < $journal-1;

	$x++;

	{
	    # Temporary restore local scope.
	    local $fjoin = \&fjoin;
	    local $dest_dir = $orig_dd;
	    update_if_needed(d_journal("jnl" . $jnltags{$tag} . ".html"),
			 process_fmt($format_for{journal},
				     title    => "Journal: " . htmln($tag),
				     css      => css_for("journal"),
				     tag      => htmln($tag),
				     hbuttons => hbuttons(@b),
				     vbuttons => vbuttons(@b),
				     journal  => $jnl,
				     jscript  => jscript(%nav),
				    ));
	}
    };

    my $mod = 0;

    foreach my $el ( @journal ) {
	my $t = $el->type;
	if ( $t == T_TAG ) {
	    $flush->() && $mod++ if @ann;
	    $tag = $el->tag;
	    @ann = ();
	}
	else {
	    push(@ann, $el);
	}
    }
    $flush->() && $mod++ if @ann;

    $mod;
}

################ ################

#### Persistent info (cache) helpers.

{ my $cache;

  my @stats; INIT{ @stats = (0, 0, 0); }

  sub load_cache {
    $cache = new ImageInfoCache
      ((!$clobber && -s d_dest(".cache")) ? d_dest(".cache") : undef);
  }

  sub update_cache {
    $cache->store(d_dest(".cache"));
  }

  sub cache_entry {
      if ( @_ == 1 ) {
	  $stats[0]++;
	  my $ii = $cache->entry(@_);
	  $stats[1]++ if $ii;
	  warn("Cache miss: $_[0]\n") if !$ii && $trace;
	  return $ii;
      }
      $stats[2]++;
      $cache->entry(@_);
  }

  END {
      print STDERR ("Cache: store = $stats[2], lookup = $stats[0], hits = $stats[1]\n")
	if $trace;
  }
}

#### Miscellaneous.

sub findexec {
    my ($bin) = @_;
    foreach ( File::Spec->path ) {
	my $try = File::Spec->catfile($_, $bin);
	return $try if -x $try;
    }
    undef;
}

sub squote {
    my ($t) = @_;
    $t =~ s/([\\\"])/\\$1/g;
    $t = '"'.$t.'"' if $t =~ /[^-\w.\/]/;
    $t;
}

################ Button Images ################

sub add_button_images {
    # Extract button images from DATA section.

    my $out = do { local *OUT; *OUT };
    my $name;
    my $doing = 0;
    my $did = 0;

    while ( <DATA> ) {
        if ( $doing ) {         # uudecoding...
            if ( /^Xend/ ) {
                close($out);
                $doing = 0;	# Done
		next;
            }
            # Select lines to process.
            next if /[a-z]/;
	    next unless /^X(.*)/s;
	    $_ = $1;
            next unless int((((ord() - 32) & 077) + 2) / 3)
              == int(length() / 4);
            # Decode.
            print $out unpack("u",$_);
            next;
        }

        # Otherwise, search for the uudecode 'begin' line.
        if ( /^Xbegin\s+\d+\s+(.+)$/ ) {
	    next if !$clobber && -s d_icons($1);
	    print STDERR ("Creating icons: ") if $verbose > 1 && !defined($name);
	    $did++;
            $name = d_icons($1);
	    print STDERR ("$1 ") if $verbose > 1;
            open($out, ">", $name);
	    binmode($out);
            $doing = 1;         # Doing
            next;
        }
    }
    print STDERR ("\n") if $verbose > 1;
    if ( $doing ) {
        die("Error in DATA: still processing $name\n");
        unlink($name);
    }
}

################ Style Sheets ################

################ End Style Sheets ################

sub detab {
    my ($line) = @_;
    return $line unless $line;
    my $orig = $line;
    my (@l) = split(/\t/, $line, -1);

    # Replace tabs with blanks, retaining layout

    $line = shift(@l);
    $line .= " " x (8-length($line)%8) . shift(@l) while @l;

    $line;
}

################ Copying: plain files ################

sub copy {
    my ($orig, $new, $time) = @_;

    $time = (stat($orig))[9] unless defined($time);

    my $in = do { local *F; *F };
    open($in, "<", $orig) or die("$orig: $!\n");
    binmode($in);

    my $out = do { local *F; *F };
    open($out, ">", $new) or die("$new: $!\n");
    binmode($out);

    my $buf;

    for (;;) {
	my ($r, $w, $t);
	defined($r = sysread($in, $buf, 10240))
	  or die("$orig: $!\n");
	last unless $r;
	for ( $w = 0; $w < $r; $w += $t ) {
	    $t = syswrite($out, $buf, $r - $w, $w)
	      or die("$new: $!\n");
	}
    }
    close($in);
    close($out) or die("$new: $!\n");
    utime($time, $time, $new);
}

################ Copying: MPG files ################

sub copy_mpg {
    my ($orig, $new, $time, $rotate, $mirror) = @_;
    $time = (stat($orig))[9] unless defined($time);

    # I'm not sure what this does. The resultant file is about 10% of
    # the original, without missing something...
    my $cmd = "$prog_mencoder -of mpeg -oac copy -ovc ".
	($rotate ? "lavc -lavcopts vcodec=mpeg1video -vf rotate=".int($rotate/90)." " : "copy ") .
	  squote($orig) . " -o ". squote($new);
    warn("\n+ $cmd\n") if $verbose > 2;

    my $res = `$cmd 2>&1`;
    die("${res}Aborted\n") if $?;

    utime($time, $time, $new);
}

sub still {
    my ($el) = @_;

    my $new = d_large($el->assoc_name);
    my $still = new Image::Magick;
    if ( $prog_mplayer ) {
	my $tmp = "00000001.jpg";
	my $tmp2 = "00000002.jpg";
	if ( -e $tmp ) {
	    die("ERROR: mplayer needs to create a file $tmp, but it already exists!\n");
	}
	# Sometimes, -frames 1 does not produce anything. Need -frames 2.
	my $cmd = "$prog_mplayer -really-quiet -nojoystick -nolirc -nosound -frames 2 -vo jpeg " .
	  squote(d_large($el->dest_name));
	warn("\n+ $cmd\n") if $verbose > 2;
	my $t = `$cmd 2>&1`;
	warn("$t\n") unless -s $tmp;
	$still->Read($tmp);
	unlink($tmp, $tmp2);
    }
    else {
	# This may take minutes.
	$still->Read(d_large($el->dest_name)."[0]");
    }

    # Get still dimensions.
    my ($hs, $ws) = $still->Get(qw(height width));
    unless ( $hs && $ws ) {
	$still->Read(d_icons("movie.jpg"));
	$still->Write($new);
	return $still;
    }
    # Scale to 640x480 if needed.
    my $r = $hs > $ws ? 640 / $hs : 640 / $ws;
    if ( abs($r - 1) > 0.05 ) {
	$still->Resize(width => $r*$ws, height => $r*$hs);
	($hs, $ws) = $still->Get(qw(height width));
    }

    # Create black canvas.
    my $canvas = new Image::Magick;
    $canvas->Set(size => ($ws+240).'x'.($hs+180));
    $canvas->ReadImage('xc:black');
    my ($hc, $wc) = $canvas->Get(qw(height width));

    # Place the still on top of it.
    # Center image
    $canvas->Composite(image => $still, compose => 'Atop', x => 120, 'y' => 90);
    # Bottom slice.
    $canvas->Composite(image => $still, compose => 'Atop', x => 120, 'y' => $hs+135);
    # Top slice. Cannot place at negative offsets, so crop the still first.
    $still->Crop(width => $ws, height => 45, x => 0, 'y' => $hs-45);
    $canvas->Composite(image => $still, compose => 'Atop', x => 120, 'y' => 0);
    undef $still;

    # Drill spocket holes.
    my $hole = new Image::Magick;
    $hole->Set(size => '60x40');
    $hole->ReadImage("xc:grey90");
    $hole->Draw(primitive => 'polygon', fill => "black",
		points => " 0,0   5,0   0,5");
    $hole->Draw(primitive => 'polygon', fill => "black",
		points => "60,0  55,0  60,5");
    $hole->Draw(primitive => 'polygon', fill => "black",
		points => "60,40 55,40 60,35");
    $hole->Draw(primitive => 'polygon', fill => "black",
		points => " 0,40  5,40  0,35");

    for ( my $v = 0; $v < $hc;  $v += 80 ) {
	for my $h ( 30, $wc-90 ) {
	    $canvas->Composite(image => $hole, compose => 'Atop',
			    geometry => "+$h+$v");
	}
    }

    $canvas->Write($new);
    my $time = $el->timestamp;
    utime($time, $time, $new);
    $canvas;
}

################ Copying: Voice files ################

sub copy_voice {
    my ($orig, $new, $time) = @_;
    $time = (stat($orig))[9] unless defined($time);
    $orig =~ s/\.\w+$/.mpg/;
    return if -s $new;
    return unless $prog_mplayer;

    # This will produce an MP2 file. Good enough for now...
    my $cmd = "$prog_mplayer -nojoystick -nolirc -vo null ".
      "-dumpaudio -dumpfile " . squote($new) . " " . squote($orig);
    warn("\n+ $cmd\n") if $trace;
    my $res = `$cmd 2>&1`;
    die("${res}Aborted\n") if $?;
    die("${res}Aborted\n") unless -s $new;

    utime($time, $time, $new);
}

################ Index Icon Maintenance ################

sub create_master_index {
    my $index = d_dest("index.html");
    return if -e $index;
    update_if_needed($index,
		     process_fmt($format_for{main},
				 title    => html($album_title),
				 css      => css_for("main"),
				 version  => $::VERSION,
				 link     => ixname(0),
				));
}

sub create_index_icon {
    return unless $icon;
    print STDERR ("Creating index icon\n") if $verbose > 1;
    unless ( indexicon() ) {
	print STDERR ("(Index icon not modified)\n") if $verbose > 1;
    }
}

sub indexicon {
    my @imgs;
    for ( my $i = 0; $i < $index_rows*$index_columns; $i++ ) {
	next if $i >= $num_entries;
	my $el = $filelist->byseq($i+1);
	my $file = $el->dest_name;
	my $img;
	if ( $el->type == T_REF ) {
	    $img = $el->assoc_name;
	}
	else {
	    $img = $el->type == T_MPG ? $el->assoc_name : $file;
	    $img = d_index($img);
	}
	push(@imgs, $img);
    }

    my $iconfile = "icon.jpg";
    my $ii = cache_entry(" indexicon ");
    if ( -f $iconfile && $ii && $ii->dest_name eq "@imgs" ) {
	return 0;
    }
    my $el = new ImageInfo($iconfile);
    $el->dest_name("@imgs");
    cache_entry(" indexicon ", $el);
    $cache_update++;

    my $image = new Image::Magick->new;
    foreach ( @imgs ) {
	$image->Read($_);
    }

    my $width = $thumb;
    my $height = int($thumb*0.75);

    $image = $image->Montage(tile=>"${index_columns}x${index_rows}",
			     texture=>"xc:gray90");
    $image->Resize(geometry=>"${width}x${height}");
    $image->Write($iconfile);
    1;
}

################ Subroutines ################

sub app_options {
    my $help = 0;		# handled locally
    my $ident = 0;		# handled locally
    my $sel;			# handled locally;

    if ( !GetOptions(
	# Run time options.
	'clobber'        => \$clobber,
	'dcim=s'         => sub { $import_dir = $_[1]; $import_exif++ },
	'exif'           => \$import_exif,
	'import=s'       => \$import_dir,
	'info=s'         => \$info_file,
	'link!'          => \$linkthem,
	'update'         => \$update,
	'mediumonly'     => \$mediumonly,
	'select=s'       => \$sel,
	'extcss'	 => \$externalize_css,
	'extformats'	 => \$externalize_formats,

        # Album options. Can also be set in info/config files.
	'captions=s'     => \$caption,
	'cols|columns=i' => \$index_columns,
	'icon!'          => \$icon,
	'medium'         => sub { $medium = 0 },
	'mediumsize=i'   => \$medium,
	'rows=i'         => \$index_rows,
	'thumbsize=i'    => \$thumb,
	'title=s'        => \$album_title,
	'home=s'         => \$home_link,

	# Miscellaneous.
	'debug'          => \$debug,
	'help|?'         => \$help,
	'ident'          => \$ident,
	'quiet'          => sub { $verbose = 0 },
	'test'           => \$test,
	'trace'          => \$trace,
	'verbose+'       => \$verbose,
        )
	 or $help
	 or @ARGV > 1
	 or @ARGV && ! -d $ARGV[0]
       )
    {
	app_usage(2);
    }

    app_ident() if $ident;
    $dest_dir = @ARGV ? shift(@ARGV) : ".";
    $dest_dir =~ s;^\./;;;
    if ( $import_dir ) {
	die("$import_dir: Not a directory\n")
	  unless -d $import_dir;
	$import_dir =~ s;^\./;;;
    }
    set_selector($sel);
}

sub app_ident {
    print STDERR ("This is $my_package [$my_name $my_version]\n");
}

sub app_usage {
    my ($exit) = @_;
    app_ident();
    print STDERR heredoc(<<"    EndOfUsage", 4);
    Usage: $0 [options] [ directory ]
      Album:
	--info XXX          description file, default "@{[DEFAULTS->{info}]}" (if it exists)
	--title XXX         album title, default "@{[DEFAULTS->{title}]}"
	--[no]icon          [do not] produce an album icon
        --home XXX          up link for index pages
      Index:
	--cols NN           number of columns per page, default @{[DEFAULTS->{indexcols}]}
	--rows NN           number of rows per page, default @{[DEFAULTS->{indexrows}]}
	--thumbsize NNN     the max size of thumbnail images, default @{[DEFAULTS->{thumbsize}]}
	--captions XXX      f: filename s: size c: description t: tag
      Medium:
	--medium            produce medium sized images of size @{[DEFAULTS->{mediumsize}]}
	--mediumsize NNN    the max size of medium sized images, default @{[DEFAULTS->{mediumsize}]}
	--mediumonly        ignore large images and links (for web export)
      Importing:
	--import XXX        original images
	--exif              use w/ EXIF info, if possible
	--dcim XXX          as --import with --exif
	--update            add new entries from import, if needed
	--[no]link          [do not] link to original, instead of copying. Default is link.
      Miscellaneous:
	--clobber           recreate everything (except large)
	--clobbercss        recreate (overwrite) style sheets
        --select=XXX        select images (default, all, hidden, tag:...)
	--test              verify only
	--help              this message
	--ident             show identification
	--verbose           verbose information
    EndOfUsage
    exit $exit if defined $exit && $exit != 0;
}

sub set_selector {
    my $sel = shift || 'default';
    my $tag;
    if ( $sel =~ /^(tag):(.+)/i ) {
	$sel = 'tag:...';
	if ( $2 =~ /^\/(.+?)\/?$/ ) {
	    $tag = $1;
	}
	else {
	    $tag = quotemeta($2);
	    $tag =~ s/(\\ )+/\\s+/g;
	}
	warn("tag = \"$tag\"\n");
    }
    my %selectors =
      ( default    => sub { ! $_[0]->hidden },
	all        => sub { 1 },
	hidden     => sub { $_[0]->hidden },
	'tag:...'  => sub { $_[0]->tag =~ $tag }
      );
    die("Unknown selection: $sel\n".
	"Possible values are: ", join(", ", sort keys %selectors), ".\n")
      unless $select = $selectors{lc($sel)};
}

################ Modules ################

package ImageInfo;

my @std_fields;
my @exif_fields;
my $exif_rot;

INIT {
    @std_fields  = qw(type seq next prev hidden
		      dest_name orig_name assoc_name
		      timestamp file_size medium_size
		      tag description annotation
		      rotation mirror);

    @exif_fields = qw(DateTime ExifImageLength ExifImageWidth
		      ExposureMode ExposureProgram ExposureTime
		      FNumber Flash FocalLength ISOSpeedRatings
		      ImageDescription Make Model
		      MeteringMode SceneCaptureType Orientation
		      height width file_ext);

    $exif_rot = { top_left   => [   0, ''  ],    # 1: no corr. needed
		  top_right  => [   0, 'v' ],    # 2: flop (V)
		  bot_right  => [ 180, ''  ],    # 3: 180
		  bot_left   => [   0, 'h' ],    # 4: flip (H)
		  left_top   => [  90, 'h' ],    # 5: flip 90
		  right_top  => [  90, ''  ],    # 6: 90
		  right_bot  => [  90, 'v' ],    # 7: flop 90
		  left_bot   => [ 270, ''  ],    # 8: 270
		};
}

my $largepat;
sub basename_nolarge {
    my ($f) = @_;
    unless ( $largepat ) {
	$largepat = quotemeta(::d_large());
	$largepat = qr;^$largepat[/\\];;
    }
    $f =~ s;$largepat;;;
    $f;
}

sub new {
    my ($pkg, $file) = @_;
    $pkg = ref($pkg) if ref($pkg);

    my $self = { $file ?
		 (orig_name    => $file,
		  dest_name    => basename_nolarge($file)) : (),
		 description  => "",
		 annotation   => [],
		 tag	      => "",
		 hidden       => 0,
	       };

    if ( $file && -f $file ) {
	my @st = stat(_);
	my $ii = ::cache_entry($file);
	if ( $ii  ){
	    $self = $ii;
	    delete($self->{$_}) foreach grep { /^_/ } keys(%$self);
	}

	# Else, get image info.
	else {
	    my $ii = Image::Info::image_info($file);
	    $self->{file_size} = $st[7];
	    $self->{timestamp} = $st[9];
	    unless ( exists($ii->{error}) ) {
		for my $key ( @exif_fields ) {
		    my $val = $ii->{$key};
		    next unless defined $val;
		    if ( $key eq "Orientation" ) {
			($self->{rotation}, $self->{mirror}) =
			  @{$exif_rot->{$val}}
			    if exists $exif_rot->{$val};
		    }
		    else {
			$val = $val->as_float
			  if UNIVERSAL::can($val,"as_float");
			$self->{$key} = $val;
		    }
		}
		::cache_entry($file, $self);
	    }
	}
	# Actualize.
	$self->{file_size} = $st[7];
	$self->{timestamp} = $st[9];
    }

    bless($self, $pkg);
}

INIT {
    no strict 'refs';
    for my $sub ( @std_fields, @exif_fields ) {
	$sub = "_".$sub if $sub eq "rotation";
	*{"ImageInfo::$sub"} = sub {
	    my ($self, $value) = @_;
	    $self->{$sub} = $value if defined($value);
	    $self->{$sub};
	};
    }
}

sub rotation  {
    my ($self) = @_;
    defined($self->{_rotation}) ? $self->{_rotation} : $self->{rotation};
}

sub html_name {
    my ($self) = @_;
    sprintf("img%04d.html", $self->seq);
}

package FileList;

use Class::Struct "FileList" =>
  [ _tally	=> '$',
    _data       => '$',
    _hash	=> '$',
  ];

sub add {
    my ($self, $el, $name) = @_;
    my $data = $self->_data;
    my $hash = $self->_hash;
    $self->_hash($hash = {}) unless $hash;
    $self->_data($data = []) unless $data;
    push(@$data, $el);
    $hash->{$name || $el->dest_name || ""} = $el;
    $self->_tally(($self->_tally||0)+1);
    $el->seq($self->_tally);
    $self;
}

sub byname {
    my ($self, $file) = @_;
    $self->_hash ? $self->_hash->{$file} : undef;
}

sub entries {
    my ($self) = @_;
    $self->_data([]) unless $self->_data;
    wantarray ? @{$self->_data} : $self->_data;
}

sub tally {
    my ($self) = @_;
    $self->_tally || 0;
}

sub byseq {
    my ($self, $seq) = @_;
    $self->_data ? $self->_data->[$seq-1] : undef;
}

sub filter {
    my ($self, $filter) = @_;
    my $new = FileList->new;
    my $prev;
    foreach my $el ( @{$self->entries} ) {
	next unless $filter->($el);
	my $e = bless { %$el }, 'ImageInfo';	# one level copy
	$e->prev($prev->seq) if $prev;
	$new->add($e);
	$prev->next($e->seq) if $prev;
	$prev = $e;
    }
    return $new;
}

#### Cache maintenance.

package ImageInfoCache;

use constant CACHE_VERSION => 3;

sub new {
    my ($pkg, $file) = @_;
    $pkg = ref($pkg) || $pkg;
    my $self = bless({}, $pkg);
    if ( defined($file) ) {
	$self->load($file);
	if ( ($self->{_version} || 1) != CACHE_VERSION ) {
	    warn("Incompatible cache version " . $self->version .
		 " -- invalidated\n") if $verbose;
	    $self = bless({}, $pkg);
	}
    }
    $self->{_version} = CACHE_VERSION;
    $self;
}

sub load {
    my ($self, $file) = @_;
    our $info;
    $info = undef;
    eval {
	require $file;
    };
    if ( $@ ) {
	warn("Illegal cache -- invalidated\n") if $verbose;
	return;
    }
    @{$self}{keys(%$info)} = values(%$info);
}

sub store {
    my ($self, $file) = @_;
    $Data::Dumper::Indent = 1;
    $Data::Dumper::Sortkeys = 1;
    $Data::Dumper::Sortkeys = 1; # avoid warnings
    $Data::Dumper::Purity = 1;
    my $cache = do { local *C; *C };
    open($cache, ">", $file)
      and print $cache (Data::Dumper->Dump([$self],[qw(info)]), "\n1;\n")
	and close($cache);
}

sub entry {
    my ($self, $file, $entry) = @_;
    $file =~ s;^\./;;;
    if ( defined $entry ) {
	$self->{$file} = $entry;
    }
    else {
	$entry = $self->{$file};
    }
    $entry;
}

sub entries {
    my ($self) = @_;
    [ sort(keys(%{$self})) ];
}

sub version {
    my ($self) = @_;
    $self->{_version};
}

package main;

=head1 NAME

Album - create and maintain HTML based photo albums

=head1 SYNOPSIS

A photo album consists of a number of (large) pictures, small thumbnail
images, and index pages. Optionally, medium sized images can be
generated as well. The album will be organised as follows:

  index/	   index pages with thumbnails
  icons/           directory with navigation icons
  large/           original (large) images, with HTML pages
  medium/          optional medium sized images, with HTML pages

Each image can be labeled with a description, a tag (applies to a
group of images, e.g. a date), the image name, and some
characteristics (size and dimensions).

Images can be handled 'in situ', or imported from e.g. a CD-ROM or
digital camera. Optionally, EXIF information from digital camera files
can be taken into account.

=head1 DESCRIPTION

For a description how to use the program, see L<Album::Tutorial>.

=head1 AUTHOR AND CREDITS

Johan Vromans (jvromans@squirrel.nl) wrote this module.

Web site: http://johan.vromans.org/Album/index.html

=head1 COPYRIGHT AND DISCLAIMER

This program is Copyright 2004,2007 by Squirrel Consultancy. All
rights reserved.

This program is free software; you can redistribute it and/or modify
it under the terms of either: a) the GNU General Public License as
published by the Free Software Foundation; either version 1, or (at
your option) any later version, or b) the "Artistic License" which
comes with Perl.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the
GNU General Public License or the Artistic License for more details.

=cut

__END__

Xbegin 644 index.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!?TE$050XC;V3
XMOTM"413'/^JSAR))B3B;0F[A%KGDT-(D+D(0!*W^`[T6EPP:0W`2A":7>+N#
XM+C4(\D9U4&?Q1QB1^"L:>M?W>$HNT7>YY][[.>=^W^$=6Y4MDO1U.;3>^!PF
XM8E)1YUTK$70FXB[`5H7/6PTY8B6:4Z)W;K!5F2A:)./W6HEQ(]>,9EW8H:)%
XMBN$U`.]Q,:)50`*5C/0FSMWRQUP/G9YT6CU'8CF7_;,S02A)Y54/3QX/?[YE
XMV#WRSM@)`0SZ``<R,.U8^A%X`BCD`>Y#0#NEW]C7'%KU%X3P\5X`J`/PO`^,
XMK,0X;V25-M20%+&/DCK5PX"9L">-G-A&'U_JJD;PI2=JQ$S$(BL()5A:==U,
XM@/<"H%X#2.T#(^%7$+O7`-0`DB&@+8C_[7KO$F``P(T,3`W"%VR.7<P:1E8'
XMLR0<SFD_7!9[-]G5?TI+?R7QD"GN&3F>5;3(D0`)XF7M*N-;M]C*-:-Q8^8V
XC2LP<3"KJ)L"8V]^UO6/?/_-HCRMMEKD`````245.1*Y"8((`
X`
Xend
Xbegin 644 medium.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!(DE$050XC;W3
XMOTZ#4!3'\2\4TM3$="!]@"8FLK*Y]@U(]`&<VSJYZ<)2)ZT./$`GZ<CN`&_`
XM*+@T<34-HHE-TS^ZM$+@`I/^EDLXGW`N]^9(/C51=NMFGJ]HC8Q8>.YJEA==
XMU>RU`,F'K^N`IIX7X1)C=`"2S^(JT*U..R^29SLT;EK(X`7ZY*@`:)],],`#
XM&5PLI5`'4(:X(+-9-3LE/WH,(#.?Z<46F<A5Q;\3'W7".8VJQ>-]/`RKA/.P
XM);Z(RL5TO`7B050FG-MO`-[[D5A,[_9/2?]%))QQ^C89I-O]O=5/]3+;_54O
XMB,,SQ/FOF].Z85(M&NKRK:08[;J86&LA6-N8H$#O*3BW-,$7[-#HI3,GS'[F
X@8.&Y(I#.;77JS^,'X]I4;J49'ZH`````245.1*Y"8((`
X`
Xend
Xbegin 644 first.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!;4E$050XC873
XMNT["4!C`\7]+"<&%@?``)"9V,`8F77D#XF4$W44G0Q"7+BI1)X75(*QBV!P<
XM8'6QQAC%B0<P#"P2PLVAI3TMK9[E.SGGU^_2Y$AM_EF*&2<]]TTT((A!JSGJ
XMND4\F$Z%`:D-/R<Z(=4M.D.2ITL@M1D4=56+1=RB_UGN),_"R-#2U>KR`B"R
XM457U%LC01%,6[@&4'$V0F8Q",9]!5P!D>EU5*/%FA.F[=2([OZK=&K'TZ"/J
XMY2D`YP]"-\+]K'YME"@)P"%,,+D4@5AE#J[N'97M''<5H]1%P]F\E:-6,9HL
XMN8"5X]6<XGGB`E:.1,[8K6_Y";+[QK;@)O8LNP<`2/D=/T'F$(#`T;:?L$A^
XM4Q3B/Y6RT@V`7)0:W@(RLQ<`CF=^@FS"B(4/H8]HO-.WR9IYO"J(0'#XC??Z
XM,F=)HXT]P;A,&A1(/>E[6M0C0[F33-EOSG/-WQP,6DTO8+_;OY?\'^`7/!!E
X1K2\A))\`````245.1*Y"8((`
X`
Xend
Xbegin 644 first-gr.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!2DE$050XC873
XM36K"0!3`\7]B1"P%H5&$?@A"H=EFUZTW\`@]0$_0;K)I3Y`#>(3LNX@WR++I
XM2M"V(-520131U"XF'Y,QJ6_S(//+O)?,/&W(D3#B',W5%;,BB;7O;4>JZ%;[
XMO3J@#6'U&%"S5!%NL)].0!NR?@@LI]50Q>+5#>WG.CKX@36X/@`T;@=6X(,.
XM'HYQL`Y@W..!3K2MM4H^]`9`9SZRI!*12/LH?:+GWYK^B#Q9E8CIQQZ`\4SJ
XM)@]$B8D$<GLDX%T&LDC!%\4B!B@@ZR,!XQG%8ODI\O=2`6F5TW.1SYIE@O:%
XMR!WU"+).$W+9*A,)T10B_[&4-,M$2CHRR=^=-IIHMU30CN_%U:]4Q>R&BXS$
XM0Z)5)%&I;I232.,M[K2/LRL$.Y<^&-![">X<LV`'-[1[V<P51C)SL/:](I#-
X;[?^A'P/\`;NE8"0C>L''`````$E%3D2N0F""
X`
Xend
Xbegin 644 last.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!<4E$050XC863
XM/4M"41C'?_>HB"T.X@<0@BX1HE.MCFVB-1K1FC9)6"TNO2`MU6T-J[$4MX8&
XM_087(L@F/T`XM"2B7AN.+_?E6/_E.3SGQ__YWWMXM!;_R#^IHZ[[)N*S$;UF
XM8]!Q$[%`.A4"M!;\G)@$=3?1[I,\70*M1>_8U,O1L)OX_C#:R;,0`IJF7EWV
XM`(0WJKK9!`$-RG[//8`_3P,$HT$PNN!#5P`$W8X>!GBW9/O-A8G9Z>5"UKN'
XM103U<P`LX]&9QG:NC4L"L*[&.4WI`?7*"(!KNXN#X/G2BS@)GBIC`&[NE3D`
XM:K)8M]K.`H*:?'3+B">44R"[+OOYA#H'V9)L[T^'N*=L%^6?*.1F+:?'5E&F
XM.)@#3H_,H?``#B)[)&T+=L`^)3,!\@[`YK&Y*NM>W!E>$(FUOP'6)G8N`($O
XMT/]"K<])CC3EH1(8&J3!#ZE7<[<<43@8[61JOG-*37<.>LV&"ICO[=_RO+Y'
X5OU]T8)`W'C>9`````$E%3D2N0F""
X`
Xend
Xbegin 644 last-gr.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!3DE$050XC873
XM34K#0!3`\7\F*:5U4:$4W`A&"V;;G=O>H$?P`)Y`-]GH"7J`'B%[%^D-LC3B
XM1P0W@A3LHBVE^7"1IDDF,_HV$_)^O'G#FS'F_!/6?DT6<J9O5L3&]W:1+.S6
XM9-P!C#FL[P+:CBS"+:/[+AAS-K>!XPYZLE@^3</10P<!?N#,A@U`[VKF!#X(
XM\'"M1A[`NL$#0;)K#S0'O000+"*G!Y`4YY:8.'Q]_>3KYTHGTF@)0/RVU@BR
XM][Q*_++2"+(H)\GK2B/(/@JRU@A2!:F+LMV22((LBG,2I1IAV/D$+%NHA;#S
XM$5K#KGH7<78,@%F"NC`4H"8,NP!'E;^5FV&<-WJHUS@T>5$#E1HG^]M_:M8`
XM@KX=+@&*A`00F*WM-^IXWO<QP8V5()XR`0O&C\&UVU=4F(:C<?GFE%&\.=CX
X?G@J4[_;OD*??C%],IF=*M!8^B@````!)14Y$KD)@@@``
X`
Xend
Xbegin 644 next.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!,4E$050XC873
XMOTK#0!S`\6^O*:6"=`B%_EL*@EF[N?8-^@@.0A'[`BI"0-07B$C!H8^0W2%Y
XM@XS&J4N2%J1#$2RE+;A4T]S%WF\Y?G<?[O<[CE_!1Q/&;MW.Y1.SN">6GKN>
XMR*)3ZO<J0,&'[]N`LB6+<$7WX0@*/LN;P+)K55DLWIVP^UA!@!=8XQ,%4#T;
XM6X$'`EQL0SD',(:X(-BNR[5_'GH*()A/K+3$E\I$-KU.="(>S#2"Z<54(Y@-
XM8HT@N8HT@F@8:P31Y50C2.YTHGFO$>V7QF'1=EK[J?JKS><,4$5]U,ANR%4:
XMKQ*016M4ER^5JCP=*WT)S$ZX^$M5@*!86GVJ^P!\[/KH8V]RP<:A#P;TWH)S
XMV\RYP0F[O73F<N-WYF#IN7D@G=O#D?O[F?@!Z/M.5(VCE!4`````245.1*Y"
X"8((`
X`
Xend
Xbegin 644 next-gr.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!'TE$050XC873
XM,4[#,!2`X3]NFJ@L':*.2$0*D#4;:V[0(W``3@!+%GJ"'J!'R,Z0WB`C@2*%
XM'77H0N6TD5@*;9Y-\Q;KV9^>GV4]9TE/N(>U7<N38'`BMD6^JZ4(A]-T!#A+
XM^'XJ\6,I*DWR?`'.DNUC&6>3L12;UWF5S$8H*,IX$1F`\=TB+@M0D).YQCF`
XM^T`.BG;G3_YYZ"V`8EW'QRM:DZEN6NL^H5=-CZ!YET0*FI7N$>@/W2,DL0C1
XMKDW0?/8)[^HDL?V('WGG:_B1SUDA@'F+=^UU-V0-[T8`6<._E$"*<&#TI0C"
XM:O.7F@#%8*B_S'T`W@Z=3LGV5K"?,P47TI?R/@LL%>95DAYGSAJ_,P?;(K>!
X=X]R>#^OO=^('W$Y.6'!/."L`````245.1*Y"8((`
X`
Xend
Xbegin 644 prev.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!-TE$050XC863
XM34["0!S%?RU%@HEA43D`B8G==L>V*JPY@@?P``90&R/&`_0`+#Q`UZ"V-^C2
XMNN(`RH*8V/!E7(BA\R%]FTEF?O/FS4O^1DR!K,VZGLHG=BE'9%&XG,A$H]SQ
XMJH`1PU<OH>+(1#K''>R#$9-U$\>OUV1B]AJD[GT5$Z+$&1XI`+7FT$DB,"'$
XMMY1S`.N"$$S6RTK]GX\>`YA,)X[XQ*>(F<K%\6,!\=)?[":>+M?L),;];]E4
XM)$8W*R670(S\A0*0[^KY6G40/#YZ.B!/'`ZTY>=SG-[N%1"T?0TB&K>-*R6+
XM]'3+Z,J5R:V?/90*"$[NI"SJ!UM-V<-NI#-AZT`F2N7YNV+TJ[=-C@Z^MFY6
XM`1VPP!LGY[ZM<0A2U]O.G%9_,P=9%.J`[=SNECH-LGX`6BE1+/?*'38`````
X(245.1*Y"8((`
X`
Xend
Xbegin 644 prev-gr.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!(4E$050XC873
XM,6[",!0&X-].(D2'TBAB)Q(27K.QY@8Y0@_0$[1+EO8$.0!'R,Z0W,`CZ136
XM"B'*T@B%B`ZE`K]GR%LLQ5^>GU_R1(F><,]KMZ4[@7,EFB)O:RI"+XF'`$0)
XM_+QI#!05U0'1^P,@2C2O6J7C$17[559%'T-(H-!J,64`H_E"Z0*00([49?L`
XMX+X@!R2Z=C"^<=$9`$AL:V4>T9E,LA=W7SWB>WTBU=`,K',DQV[-3I4$G.X+
XM&S`$*Y**MK:!:^&%HD?@:6(C1J6^C9BWM1'2,7_"!.VZ+VC;V9=CY?*_RW^D
XM.8*PVAN/'"H<[[!AB?[B\UQ'@O1H!<<,">`"\5(_IX$E0U9%\67FK/$_<T!3
X?Y#9PF=O[P:>!QB_1RTX:[.:Q%@````!)14Y$KD)@@@``
X`
Xend
Xbegin 644 up.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!)TE$050XC;73
XML4K#0!S'\6^2%IK!$FA?H%,A3]#)1Y`.3I+!P2(:'\!"44=?((LX]@$RMRJ9
XMQ+%3H#AT2=,B+92ZE+01E[8F,<GAX&^Y/]P'_G?'_24'00K;-9PG=RI*1,RZ
XM[GJ4%+6B;E0!R8&W^P742W&P&H)VW0#)87:V:)J4E;@(EUBV]EA%.>5AT.RH
XMJIQH(JOJX<<@:""#BYEQ#1,79,)U[F61F8_JY7P!)44D1%WB>3J:Y(M^QS\?
XMYXG>[0;?]+)%[RX`O*MQEGB^"0#P+B;IXJ6]V59^:YHF^NVO?3UM^?MZ]X/X
XM?#^)=G\]_B4.+DG/W]_T_\0JS!>5VG"9+Y2BL(N.E;%IH4,!#,=._^V6K1F1
XEF4O+;N9@UG73P,_<"D\JR#<E0U*RN@H0E`````!)14Y$KD)@@@``
X`
Xend
Xbegin 644 journal.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!I$E$050XC863
XM/4B"412&'_4STS(CZ8>@(0SZ:@E)I+8<B@@"FYJ"HBD(6FHI")=L<71H#)R:
XM<JBE(%V:"KXA(B.BJ:F"I!\QS1JNM_Q^K+.\EW,>[GG/@6/+\D\H5?U\,E;\
XMCAJBD$F7[HQ$KS,:<0.V++QO:+A4(Y$K$MSR@"U+85U38^T^(Y&_2N:"<3=V
XMR&CJ;I\)P#>RJVH94"!-3"D>`LYI+FY$>:0;4):7TU,H?)9<[;S'@>9I3E*"
XM2'0#](M9GNZ&?+B7@`88;:X.8MI'XZ+0<-CDITJ43F6B8Y"S-V#8JR=>5R4Q
XM$2=Q"Z0&](1K5A(J3#X";88NGK6:S@M6/LJ7PG"_R:@D7L0L@;VZ1,,$`)U0
XMWI2E:+B6:(K+?.5(OD(ZHG(OM-4+'=L`!_OZ+OD9H2MSX!H".#?X4$)"N^HZ
XM]>Z82WKBZPT`NP>HO`)\&(CG<:"ZC_LQJS\<`0!ZP!:0I19)^'MS>5_+SS*=
XMQK7:<3B+#W5,7@N"*+&R)5!.$@4%(L?:?,QO\4,R%XS\WIQER)N#0B9M!?S>
X;[=]A_P_@&\BG:"&P_Q,B`````$E%3D2N0F""
X`
Xend
Xbegin 644 sound.png
XMB5!.1PT*&@H````-24A$4@```"$````A"`````!RCYVS```!FTE$050XC9V3
XMOTL"<1C&G_/.S"(;5"@20HF\P7Y($$5#V"@$_0$5$FT);0VYN"31$`5"2X1+
XM0TO<%$3#'10M#5<.>0:A!(:D$DJD<ITTJ.?=^2MZE[OO^WR^[_N\W+T$ARY!
XMU9Y23JN820519!DQH27L^A6/$0#!`=\!'@9:2PAEN/?Z`()#<9>G@]9!+9&/
XMA05WR`@=P/)T9*P)$*.%,YIG`0I@$*14HE2(/0K1+]S[_8P7%"318&VHJ?0S
XMGWZI'9S567*)*;G%Y452TK93U_]\U>J`KCG5L48M2*-K9'X_VX88'%BP3S@!
XM'+>IL;%JZN)C2`O\P>D_9\E7LA93!R)Y^"`"XQ]MB:>M$@#4OXU,I&^368LM
XM!>"\U-+']5&FEJB\:VI69WD+9.3$4OW-12F(.\6=];6J,AO2*;J,RGH_J.UE
XMK@S,3/?4?9CM0GYN,EH]THL`'`ZU#U)?SI`G?AN`X<W37J48!P""P]4!':$@
XM20!)JJ;X\0D[7E"`YX;W!<UHBGA8<'L:.]<RZCL'%%FF%=#8V\[1_?_X!<SK
X2=/BT1/6\`````$E%3D2N0F""
X`
Xend
Xbegin 644 movie.jpg
XM_]C_X``02D9)1@`!`0$`2`!(``#_VP!#``4#!`0$`P4$!`0%!04&!PP(!P<'
XM!P\+"PD,$0\2$A$/$1$3%AP7$Q0:%1$1&"$8&AT='Q\?$Q<B)"(>)!P>'Q[_
XMVP!#`04%!0<&!PX("`X>%!$4'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>
XM'AX>'AX>'AX>'AX>'AX>'AX>'AX>'A[_P``1"`!:`'@#`2(``A$!`Q$!_\0`
XM'P```04!`0$!`0$```````````$"`P0%!@<("0H+_\0`M1```@$#`P($`P4%
XM!`0```%]`0(#``01!1(A,4$&$U%A!R)Q%#*!D:$((T*QP152T?`D,V)R@@D*
XM%A<8&1HE)B<H*2HT-38W.#DZ0T1%1D=(24I35%565UA96F-D969G:&EJ<W1U
XM=G=X>7J#A(6&AXB)BI*3E)66EYB9FJ*CI*6FIZBIJK*SM+6VM[BYNL+#Q,7&
XMQ\C)RM+3U-76U]C9VN'BX^3EYN?HZ>KQ\O/T]?;W^/GZ_\0`'P$``P$!`0$!
XM`0$!`0````````$"`P0%!@<("0H+_\0`M1$``@$"!`0#!`<%!`0``0)W``$"
XM`Q$$!2$Q!A)!40=A<1,B,H$(%$*1H;'!"2,S4O`58G+1"A8D-.$E\1<8&1HF
XM)R@I*C4V-S@Y.D-$149'2$E*4U155E=865IC9&5F9VAI:G-T=79W>'EZ@H.$
XMA8:'B(F*DI.4E9:7F)F:HJ.DI::GJ*FJLK.TM;:WN+FZPL/$Q<;'R,G*TM/4
XMU=;7V-G:XN/DY>;GZ.GJ\O/T]?;W^/GZ_]H`#`,!``(1`Q$`/P#ZI\=^,O#?
XM@?0I-9\3:I#I]HG0N?FD/]U5ZL?85\I^._VQ]5O]1.F?#?PJ7W,5CN+Y2\LG
XMNL2<#\2?H*X[_@H)JHU7XCZ)-;27!LUTPQJK,?++K*Y+JO8D,H)ZD!?2O1/V
XM8_!EA;_!+2O&-MIEK%-=^8LUP0#-(RSO'UZA?DZ<<T`>*I\1/CG\1_'/_")O
XMXOO--U&0RAH%G^R1QE068$KR.F/RJ;XD_#SXM^$_"=UXEUSXA_;K:RV!DAUJ
XMXDE^=U08#`=V]>F:T?A[%8P?ML20:A=I:V4M]>F>620(%!MI'ZGIS@5Z/^U]
XMJOAU/AQJFEZ%K=E>B26V^6.Y21F&]6/0^U`'D'P>M?V@-=\/W/B/P%K^M7=I
XM9SM;R0_VGN;<%#D"*1CD8<<@=37H_@W]K'QSX/UI_#_Q2\-O>/;D+.8XQ;W<
XM60""5/RMP0?X>O6NG_8_\/F#X%1ZW:ZE!'/)>7$\D*R8D`5O+SP<C[@Z^OTK
XMY^^*DG_"3_M0W$6H,]VMSJ]I:3;NKHHCB(]^%QF@#[_^%OQ3\%?$FR:X\+ZQ
XM'<3(,RVDH\N>,>Z'G`]1D5W61ZU\/_&/X&Z9X3TB[^)?PE\37.C-I*&YGLY;
XMEDEB`/6)QSG)QM/7UKH/@#^UM:W2VOA_XFC[-.1L36(T`C8^LRC[O^\!C/4`
XM9-`'V!15>PO;2_LXKRQN(KFVF4/%+$P974]"".HJQ0`4444`%%%)O%`"Y'K1
XM7"GXD:%/\68/AUI\\=WJ0LIKR\,;@K;*A4*C?[9+9QV`]Q10!\:?MQ6GE:KX
XM>G"][N$D=.&C('ZFN%\*?$7XJ7GP_L/AMX,%Y'IUNTCN+"$M-*SR%_FDZJH)
XM'`('KFO6/V\+/;;:?<;.(M5N(P1T&\9Q_P".?H:L_L?RH_PQNE^4O'JDBG`P
XM=NR,CGZDUPYEC7@L.ZJ5SNR_"+%5O9MV/+]._9]^)6M2O>:K)I]G-)\SF\O0
XM\K$]SLW?J:WT_92\7.H(\0:1_P!\O_A7U9IYD`!ALXQ_M.:UX6<K\Y0G_9KY
XM9<0XJ;TLCV)Y31AW?S/A_7/@3\8/"$37FD037L2C<SZ5<L7''/R<,?P!K@/`
XM?BO^QOB?I/C#7H)-7^R:@EW=1NWS3$-D\GOW_"OT=\4:D="\`^(M:BNU4V6E
XMW-QL88.4B9N.>N0*^#_V6O`&E?$CXIC0]=2:33(K"XN;@0N4<`*$4@CIAW4^
XM^,5]+E^,G6HN=5;'A8FE&G4Y8'M'QX^)G@;Q;\&-9N?!6H2(]PL,4]G,2DT+
XM-,N5V]P0#R"1[]JXK]D[X>>"/'7A'Q3;>,;24&6XMXK&_B)62U=5<M@]"#N7
XM(((.![$-^+_[+?B?PQYVH^$;@^(=+&2(Q@7"+V!'1C].OIVIO[-_Q7\-_#[0
XM=3\$^,=#N;:6:]>YCOU4AX)"B)Y<B'G`V9[]2,5VTZT*BO%F$H2CJT9<OC;X
XM@_LZ?$?4/"6A>*8=6L+-T8V\H\RVD1T#K\IY1MK#.TBOJ#X6?M6_#OQ3!%;>
XM(ISX8U$A0RW9)@9C_=D`P!_O8^M?&/BB[M_&/Q_>6T=+JVU#6X886&2LD>](
XMP>>V!7H'[8'A/P5H?]CZOX<T1M(U#49IA=0P29MF50IRJ'E&RW0''XUJ2?H)
XMINJZ=J5JMUI]];7<#C*R02JZG\035II%4%F95`YR37Y<^#/AY\4+_P`+6VM^
XM%I;I=/O`S(MMJ'E$[79#D9`ZJ:R_!T_Q#\<ZO'X>TOQ'JUQ,8F;9-J,@0(.N
XM<GI_C0!^FOBOXA>"?"MLUSX@\4:381JN<27*[V^BC+,?8`U\I?&_]KB?48)M
XM!^%EM<0-.#$VJ31?O<'C]RG.">S'D9Z`\CYVG\!7]G\5K#P1KEVJW-S<V\4T
XMT)W[1+@Y&>I`->OVW@/POX(_:+^'NDVTSVVGW",]U<W4P)9AY@W$G`7H!T`H
XM`I?L4Q:K:_M.PPZN+A+Y[*Y:Y%P3YA+1AOFSSDY!YHKI_A/J^DW_`.WY/=^&
XM[RWO-*O&N8XIXLE'5;,YVD]?F3KWP>O6BBX&]^WE9[O"LDH3F'68G)!Z!HI.
XM?U'YURG[&-SO\)Z[:;@?*ODDVXY&Y,9_\<_2O2?VY;/S/`>NR>6#Y4MK,#GI
XM\R)G_P`>->0_L53<>*+8E?\`EU<#O_RU!_I7C9_&^`G\OS/6R65L7'YGTU:?
XM9RPW0RSMZ#I7060"HNV+R!_+\JP;-[D#"3QPKZG`K:T]P<#[1]H;T/\`A7P5
XM$^FQ:W9R/[5&HRZ1^SOXFG\^"0W,45JA48)\R558?]\EJ\=_X)SZ,6U?Q=XB
XM="1!;P6<9`Z[V9V_]%I^==5^WEJ4=I\)-)TQ+=[>:^U9&?GATCC<G_QYEK=_
XM8(T0V7P/GU(Y275-3FE5P!G8@6,?^/(]?=X;]W@;]SXZM[U8]JU&.W=GDM)A
XM')_'&>,_AZUYE\1OA=X-\=P/_;FE1B\8`"\@&R=<=/F'7Z'->D:J;A&V7<,;
XM_P!R51@FLF5U1"S%L'T&:^9K5I4JO-#W7]Q[.&IJ4+2U/C3XA_`SQ)\,[C_A
XM,O"^OQ75MI;BZ65T$4\!4Y!P<J^..G7TKS#XC?$+Q%X_-@_B`V\DED)!&\,0
XM3=OV[B<<?PBOL3]IV_\`LWP0\1R1W:2K+'%#M9<M\TJ+U^F:\^_99T33;_X0
XM31ZGIUO>PW.IS/LN(0Z'"1KT8>J_SKWL-G$X8-UZRO9V.:>61J8CV4';2YA_
XM#WX[>"?#O@'2M#ET_61=6-JL4@$2%'?'S,IW9P6SU&>:\W_9O\7^&O!/C'4M
XM9\327@A;2Y;>V6VBWEI6>,KGG@84U]*W?P@^'-S,\TGA6S#,<D(SH/R#`5D>
XM+/A%X"MO">LSZ=X;MXKQ=/G,#AW)1O+)4C+$9!%.GQ+A9R4;.[+GP_7BF^9'
XMA.L^.FUGX\V_COPYH5YJ!MI(9H+.1#N=HT`!.S/&X`_3TKT:V^%/Q"^,.L6V
XMN>.KBU\/V4*;(X8HMTVPG)`3/'U8\9Z&M_\`9%^RR?#*0B%1*FI2I(RJ-S?*
XMA&3_`,"_2OH73$*Q<0")".`3DGWK#'YY5IU94:<;6ZCH953=*-63O<^8/A5X
XM9TKP#^W!HOA[2O.^Q1PNL6]MSY>P8DD^[$G\>/2BMZYC2#]O_P`-3%L>=$K-
XMN/<VLJ@?H**]_"S=2A"4GK8\3$02JR2/2_VR[3[1X!\1IY8;=IT<QR?[D@.?
XMPVBOC?X+_$5/AW/J]S_9YO9KR!$B&_:%96)Y/7&":^YOVJ+/[5X*UM-@9I-"
XMN0N3P2$<C]2*^./V0O"NG^+_`(M'3-0T^WO1%I\MQ"L_W$D5TPY'?`+<'(R0
XM>H%:UJ,*T'3FKIA2JSHS4X/5%#Q/\:/B=>[+@W[Z3:SY,*V]N$#`>C$$GKUS
XM7H.E:1^TM;P+=VOB3'R[DB>\B;</H5*_G5?]M6P2VF\+RPQ!%5;J$[0`HP8B
XM`!^+5]0V^C6-A\/M!U*WU#S_`+1:6W!((.Z'.016<,%AX*R@ON-)XJM-WE-_
XM>?"?Q3^)_C7XAVNFZ5XJEBN9-&>?RVABVL=^T-NVG:<;."`.IKZM_96^,/PR
XMM_AIH/@2XUC^S-4LX"CQWD?E)+([EV*/RIRS'`)!]J\%_9;TE]5^-.M,+0W2
XM6ME<R.NW=@&5$R1W'S8K2_:Y\,^&]!MM(O=,TF"QU*]N)!*T0*;E0#.5Z9RP
XMYZU53#PG#D6ABIM2N?;5Y(1%OMKM+NU<9P6#8'^%93DA243<?05\?_"VQ^-6
XMA^#-,\2^$-;CU&RND,K:7=/O`4,0``W`R!GY2.*],\(?M'>'[B[;1_&MC<>&
XM-6@?RYO-1GA5P<<_Q+^(_'O7RF891B(OGAJO+?[CV\'C*5K-V]1G[9=U:P_"
XM-D16CGN-0@B*GY=PPS_C]VG_`+,MK/;?!/178.J7#SR\]/\`7.O\E!KE_P!M
XM'Q!9:KX`\.KIU[9WMK<Z@\R3VLJRH^R,@\CH?WG2O0?@=9I;?"#PS$DNUFL%
XME:-CC[Y+?C]ZN;%0]GE,(M;R_P`ST<)+FQS?11.P'2H+R!;BTFMWP5E1D8$9
XM&",5.*",U\U%VDF?1R5TT>&?L;331>']?TQ0!-;:D&8$<@L@7G_O@U])V!8#
XM$UR)9"/N@]*^;/V<D6P^)7Q'T<[E2/4`4&/X5EF&<>^5KZ!.JZ3HNF2ZAJ$\
XM&GV,0)ENKF0(OYG^E>[F<7+'2MJW9_@CP*#2PJOTNOQ/$?',<4/[<G@-QA3+
XM';LQ)X)W3*/T`%%<=J7CS2/B#^UMX$U+189!:6FI6=HDSC!GVW!;?CL/GP,T
XM5]S@H.&'A&:LTCY/%3O5;CL?7/QWM!=Z+Y&T'S[2YA.[H<J*_/'X'?$:[^%O
XMC8^*;"QCOK@6DMNL$CE4._'+8Y(!`.!C..HK])?BM&KV%FQ&1YC*1ZY'3]*\
XM*\#_`+/GA;372YM?#<FI39)$M[\RC_@)^7]*ZC$^3/'WC7QE\4-8\Z_A>YV2
XM.\%I9VY*1;CS@#)[=R>E45T?XCK&D:Z7XK"(`$46]QA<<#`QQ7Z+Z=\/=1MK
XM58;:VL;.)1Q$F%`_!1@58?P-K2@D/;-CH`Y_PH`_/+P%XJ^(7PGUYO$.D6M[
XMI=Q,A@E:[LVV3+D,4;>.>0#Z\5/\9OBIJ?Q.FT^YU73K:RGM/.9_(9MDC2%2
XM2%/W<;?4]:^_6\/^(K#=LM79#PRH0RL/0CO7G&K_``J^'6JWDH\1>$XH_,5@
XMSVJ_9Y58_P`0Q@9SF@"U\'H_"]S\'-`D\/\`B:RU">RTVV2]M4<&2&8HH=2!
XMR`&+<D<XZFODGPS90>+OVCFM[J%;BVN];N)98V&0\:N[D$#V6O9O&/[->G::
XML>L_#7QO>VUZC_);78*LG'7S$P1R/0YKS[P#X;^)_P`&?'=IX[E\"3ZS#:^<
XMFY%,L3AT9&8,F2#ACR1WZ4`6_P!IGX=^%?!_A^QU/08KFUENK[RVM_-+0[=C
XM$D`]#D*!STSQ6]\(/%_Q6;X?V-W8>'=+US1K0?9(8U;R;G9&`O!!P?3)!Z5Q
XM/[1OQ9TWXF0:7]@T:ZTB2WGEDN;64@JA*H%"D?>_BR2`?:O<OA1-X=\/_L[Z
XM1<67B#3[F]33[BYN;5)E\R%SND(8`YX#`=*PQ&&HXB/+5BFC:CB*E&7-3=F<
XMMI?[2/AN=S'>Z)JELX'/EA9`,#GH0<5=E_:,\"JC%(-4=@.%\@<G\Z\^_8L\
XM,7VN_$+5-7MX/.32].9F;.,/(P`Z^P>J?[7UBL?Q)TM[:*-/M&EH-L:X)82R
XM\GU/('X5Y3X<P+=[/[STUGN+2M=?<8UK\6KC1/B%XD\5>']+C`U<;42ZR?*R
XM5.XA3@G(/?O4/CR3XD^+/!B>./%6H.^C&18[6,N%1B25RD:\`?*>:]G_`&J]
XM(L=!^$\>AN;2VO;6XMF\A2@=P%(S@=?O9->6^)O'NGZY\(=`^'&@Z=>7VH)%
XM"9I%7A95))11C+=>O`^M>I3PM&#YE'7:_IL>;4Q-6HK2>G^9W&L^%].\$?$C
XMX`RZ;8QQ_P!HFQNYRH(\R22:+.6ZD@,#^/I17=_LT?!'7-<US3O'7Q/U&XN;
XMC1!!%I.G-+N\D1*OEECT&W:I"CN,GFBNC<P/KJ:"*;:)HDD"G*[E!P?7Z\T\
XM*`,`#%.HH`****`#ZU6N[&TO(S'=6T4RGLZYJS10!R6I>!M.GRUH[VS>GWE_
XM6LV/1?%6B+MTZ=+JWSS$#Q_WR>GX&N_I#WH`^?\`7?`?A+6->:\\8^#+2]W,
XM[3%K8+(=V?XA@]3GK7&^-_@1\(]0>)O#FFZCI64(E6*Y?`/88<M[]Z^L'1&R
XM&16'N*Q]8L+%T!>RMF.[O$I_I0!\W^#?V=[/PK;7%]X5^+^OZ"][`IN(;61%
XMWX!(5O[V,G!/(R?6N1N_V>H_$^J?;?$_Q+U:4P1A8Y+B+S7QG.%YX_QKZL;3
XM=.W?\>%KU_YXK_A3FTS3=I/]GVG7_GBO^%`'RO#^SGX2FU@S76K:_KP^4_Z0
XMX5Y#CG.,G&?TKVGX;?!?0M`(DL]#M-)C8#<57=/(/0L<M^M>O6%O;PQ*(H(H
XBP%Z*@%63]X#MB@"&QLK>QMEM[2%8HUZ!1^OUHJS10!__V0``
X`
Xend
Xbegin 644 extern.jpg
XM_]C_X``02D9)1@`!`0$`2`!(``#_X0`617AI9@``34T`*@````@```````#_
XMVP!#``4#!`0$`P4$!`0%!04&!PP(!P<'!P\+"PD,$0\2$A$/$1$3%AP7$Q0:
XM%1$1&"$8&AT='Q\?$Q<B)"(>)!P>'Q[_VP!#`04%!0<&!PX("`X>%!$4'AX>
XM'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>
XM'A[_P``1"`"``(`#`2(``A$!`Q$!_\0`'0```@(#`0$!``````````````<%
XM!@($"`,!"?_$`#X0``$#`P(#!00(!00"`P````$"`P0`!1$&(1(Q00<346%Q
XM%"(R@2-"4F)RD:&Q"!4SP?`D--'A%B5#@K+_Q``<`0`"`P$!`0$`````````
XM```%!@`$!P,!`@C_Q``Q$0`!`P,#`P$&!04````````!``(#!`41(3%!!A)1
XM81,4(C)Q@2.AL='P%4.1P>'_V@`,`P$``A$#$0`_`.FJ***BB****BB^+4E"
XM2M:TH2D94I1``'B2>0I$:SO3>J9ZY<I+C]M4K%OB+6I+?=C;OE)&,J6<D9Y)
XMQ5R[8;^@,C3,=TCOFP]<5H5NAC)`:\E.$8\D@[8(I9*=4ZZI:L#/(`;`;8`'
XMA25U1=W1#W:$X/*.VND_NN^RM.DM3W.P\+4(JFP`,&W/O'W!XL.*)X3]Q1X3
XMO@@FFMIR_6S4$-4FVOE?=D(?:6DH=87]A:3ND['UQMD4B&SCG^OK4C$?=:F-
XM36)+T2<R.%J8R!Q@?94#LXC8>ZK;;;%"K1U6^G(BJM6^>0K-9;&R_$S0I]45
XM2=+:Y;D.LV[4*&84QT\+$IL_Z62>@2H[H7]Q6_@3FKM6B05$=0P21'(*798G
XMQ.[7!%%%%=ES111144111144111144147JF]1=/6*3=90XTM`!MH*PIY9.$H
XM3YDD>.-SR!J4I&=J>IQ>M0^SQ7.*!;EJ:8X3E+K^X6[ML0GX4G?ZQ!WH?<ZY
XMM%3F0[\?56J2G,\@;QRJY.F2I4MY^8]WLN0Z7I3B?K.';`\$I'N@=`*^-'_G
XM;U\:TV>@]-ATK"X/OA+,*"@NSIB@U'0G89)^+/3%9.\2U<_ESBFP!L;?0+:8
XMO%N5.5"$E(=!*<8V)\,\LU-->)`&_4U:+1HVQG3#-AEQ&Y+205+>`X7%.GXG
XM$JY@YY>0`.:JU^T[>]'@OJX[G9`!B0E.76!X+'AY\O3E1V\=&U=%")6_$,:^
XMB%T5\I:N0QM.#QGE;:"%,K:<;2\RXGA<:<2"A8\"#5@TQJ>YV-*8Z`_=[8D?
XM[5:^.5&`Z-*)^E1]Q7O#8`G&*K$&0Q*92\PL.()YYY5[=-CT^J*6[==JJV29
XMC.G(*)U%-'.W#@G79+M;KW;T3[7+;E1E$CC3D%*AS2H'=*AG<$`BMZDC"ER(
XMER_F4*6J!<-@J0$\:'Q]E]OZXZ<6RQT.P%,32^LHMR?:MMS:3;;HM)*&RYQ,
XMR<<U,N;<7CPG"AU&V:U*T7^FN3<`X?X_9+-7020'(U"M5%%%'50111144111
XM6M<YL6VVZ1<)KH:C1FRXZL]$C]ST`ZD@5X2`,E>@9.`JCVNZH58K&+?">[NY
XM7!*D(4D[LM<EN>(.^$^9R/A-(D*!PE">%"1PH2.0`K;U5?I.HK]*NDK*5O*`
XM0WG^BT/@1\AN?$DFM!K<=#^W.LUOEP-9-I\HV350TP@CP=RML.(;0IQ:@$(!
XM*B=@D`5:NP_3;VHK\+S)!1WZ"F-M_1CC92QYJ/NCR)Z$54(ELD:CN[%FCMNO
XM-E7%(#0]Y2<[('F?TY\@:ZBT19&]+Z>2IZ.AJ4XV.\X<8;`&R!C8!(P-O"CW
XM2%I&M9(-=FCU\_9!K]7X'NS.=7'P/'W57[4KPSIQIBR6AIN.%M=X^Y@%:AD@
XM#)]#2R;U3<HK2N[F.\*LX05$IP?+E6_VUWD3[\P$DI0E!3Q_:WW'R_O2TN%U
XM2GW$'`3M1FO,@J7-<5F\M1W3GM5@"4NS4R8!:@S'UCZ,#A8=).PX>2#Z;?AY
XMU,1Y3G?KASF'(DQLE*V5C&]52V19$E<2<J4W[*'$*6<'(2#OMUY5:I%QE:CG
XM.W%_^@I>&-MP!USS\L?]4F7^W4KH3.#AP6@62YUK9&4T[>X.V/@?5;7(=!L/
XM,UFHH<85&DM(?C+5E33F2"1R4,;I4.B@01XU@.6.6W(5DVA;CJ6VTE2UJPE*
XM=R32)&]T;@YAP4Y.`(U5RT7J:=&N$"UW&2Y/@SUJ9BR7O]PPZE'%W3JN3@*0
XM>%>`K((4#G-,6EGI"U"XZECH:`,"P.J7(>&XD3U(X>!)^RTE6_WB-N=,RMLL
XM<E1)1,=4_,D^N$8E/8BBBBBRIHI+=O>JP_,1I6$\.[8`?FX4/?7S0C;H.9\\
XM?9I@=J&KHVC-*2+HZM/M!!1&0<'*\<R.H'/S.!UKBR7=95[O#EU$EQNYJ65>
XM^O(=!).`3];Q!^+?J<$1=Y"83$TX)3/T]9G5A,SM`-OJF`VHE6<YWW\MJV6\
XMXVW.W/E5:T]?FIRO9Y0#$Q.<I5L#Z?\`%61K?',[CGR^59S40OA=AP1:>GD@
XM?V/"?G\.%IMT6RS;J'&7I:W`A:@1Q)]W)R.8Y\._V21L:F.TW5`;;]BCKPXL
XM8V^J/&D;I._3++/1)BOK;5@@E/PJ&>2AU%76(576[&Y*=#I("@WG.#T]4CGG
XMTK3.C[W13N;3S88]HT'!^GJL[ZDHJJ%I?'\37'4\A1VO=+7F]6&$[:0VIZWM
XMN%UIQ7"3QX42#XI`WS2=EZ1UFPEUZ18;AW205K=0T5H`'7B3D?K3BGZC?C=H
XMUH;C-K6W`E)0$@;J43A:N?AD>@(ZU8NV#6^I-.ZF;DLPX[]F4A+;+6<F2ZKH
XM%`92K.P&X(R?0J8Z>Y333-/:&YR3MH@5TL;K;%%)([61H.!QG.$C='Q;O-D)
XMM"UJ::4`MQ.V66NN3T4?#I^=,8(:;2EIA(#38X4`;``5Y6^$JU0G#)2T;K<'
XM/:)Q;`"4J4<\*0.0'(?GUKT!RG.<['TK$[[<_?)RQA^!OY^J?NG+=)34P?,<
XMN/Y#POHY8\AL.E;T)N<DQH]J25WFY*4U!`3D,H']20H>"!R\2:U&NX0AV3,<
XM[J''27'U_92!R'B3R`\:IUO[0+W;=6N:FMZFV7%([E$=:>)"8X.S7Z`DC&]&
XM.B^F9;S5=V/@;J?V5B]76.@AR[<KI73=K@V6QQ;9;DD1XZ.$%7Q*5S4I7WB2
XM2?,U(U4>S[M*TQK=2(BW!:+XH8[AQ0P\?N'DOTV5^6:N+[+T=80^CA)^%0^%
XM7H?[']:UB:D?3N['#&$K15+9AW`YRL****KKNN:/XQ)TE5[M-L[P^SB+WP1G
XM8**U`_GPI_(5S[@?+]ZZ@_BPTG,G1(.J(;2WFXC?L\L)&>[1Q$I7Z9403TV\
XM:YB"3@>.`*H5=*7_`!!/73MU9%&(2MQN0W+"43'"V^G'!)Z^07C<CSYCS&,6
XMFP:C<COBWWHA*QP\+Y/ND'ED\N7)0V/ZU2/`_E6U&E)[H19:2[''PD?&V?%)
XM_<<CY'<+M32->WM<-/YLG22**LCPY.)A04D$'B!!Y<C4W8KJ_;G1PJ);R,H!
XMW'F#TI1V"_2;(IMF4LR[:O9MQ)SCQ`].J3R_=BVZ4Q+C(D1G4N-+`(*-Z4:Z
XMB?3G/'!_FQ2M6V]\!PX9:5>)#(GN/7NURT1[DIM*6W5HXDH((XB1T)2,<B.M
XM1$.X/:FO:]2SR3:K65,6QI8P'7>2W<'S&!X"M6VRWH;X=95PDDY!WXA6]+G+
XMEA`[M#3:1LE.PR3DGUJS-U/6OMQH#L3DNY.?/D^J5I;%'+6-J'.)`&W&FR]5
XM.K?>4ZX2I2E9)/2O5L%82`.(G`'@:U&SU\,[GI6IJN^HT_95R4J_UCV41Q]G
XM;=?RSMYTO4=%)6U#8(ADDHW+*V&,O=L%':TO3#MP1IYESC8CJ[R60=G71R1^
XM%/[^E1:^.2CZ#NW-O@R`?UJBQKD&I2GG7``H'B4H\JWK-.<?N@G0H_?%G`+B
XMR0V4@D[G.PY[_O7ZFZ:LL=GHFP,&O)\E8[>ZQ]?4&0_*-E[WJ+=V5+>;LKP:
XM0.(/)"DXQUR$XV\<TQ.RG^(>Y60HLNMF'[O:CA(D'"I#(\R3](/7WO,\JB7M
XM97B\2Q8-/0ESUNY"(C#1+>/O`;NXZ\6$=2FKGH/^'VW,`7SM&G-M-#Z0V]EP
XM)'/_`.1P?_E&!YT4KY*4Q8JAKQC=<:`3=WX7W/">UEF6C4=E;OFEKDU=+<YU
XM;5E2".:<'<$`CW58(VH0M*QE"@1Y=*C++J"WLM,6325I;@VEGW2ZVV&VT#[H
XMQN?/K7GI)3*X<I3,IV7F4OBD+(^D5A/+&!@;)VVVI$E8!DCS_,IIB><]JEG6
XMVW6EM.H2XVM)2I*AD*!Y@CJ*YF[<^Q5=I6]J72$=;MNSWDJ`D%2H_4J1U4C<
XMG',>8Y=.45Q:["LM<6G+3JOSI"3@#R2/W_XK$CKY5TYVY]BB)Q>U+HV,E$O/
XM>2K<@82]C<J:'17/W>1Z;['FAQI;2RRXA2%H(0I*A@@@D$$?*JE31M>.YB<;
XM-?RTB.4ZK[$E+CDIX4NLKQQ-+^%7GY'GN-ZF;-<Y5F<,VUN*>ADCOF%G=/XO
XM[*&Q\N50&-O45G&>=CO!UE?`L;#;(QUR.1'D:7JBFR""$^Q2QU+,'7*=6G+U
XM!O,7OXC@"A_4;/QH-3C1Y=.6YZTC;=(=;DIFV9:HTM'-@$D+'4I\1XI._AGH
XMR]%ZMB7M(CN%+$X<V^CF!S2?[<Z3KC:71Y?%J/'(0&OMAAR^/5OZ*W]ZTPRN
XM0^ONVFD\2U'F!2=UO>Y-YNJI/=K6WGA:;2L`(2.62?\`.=6'M*U#NBR0U?>?
XM*3S(Z?+EZY\*@]+Z(OVJ7DAE!C1"=WE@X(\A];]O,5H_0/3[:2/^H5&A.V5F
XM74ER<]WNL6OE51EU;\U#0W.<A#9XLGIDXWWZ`4W]"=DNH=1,-+U%+>M%E2>/
XMV?.'5_\`UY)]5;^56G2^G](:%6EN)'5=[YR/"`M:5>9Y-C]?6IV?)FSEI1?Y
XM2FT*W;M4+=2ATXO^\"M`J+L[';#IZI;CH6Y#I/\`"GM.RM.Z3C&S:%L[<F4=
XMG7T[@D=5N'=1\N0\JR<6]/G!5UD.7B<#E,1DX9:]3R&/F:CDCV=E#,Q8ML=0
XM]R!$]YYW\1Y_L*V+C<H=DM@>O$QC3UN/P1VSQ27_`"`&Y^7*@<LN[Y#]RB#6
XM\-"M%F4H76.Q)E(7)2?<B1Q]&U^,C^]2VF52%PY#DEQE3AE.`I:^%O&$\.3S
XM.V3YGI2&O?:3<7F_8-+Q/Y'!7L'U)XYD@>0^KGY^HIA]@UMOL&UW.3=6WF6)
XML@/L-O'*R2,*6?-6,GSH4VXLJ7F.(9`YX5]E(Z-OM'Z>B9=%%%=5ZBE%VW=C
XM\/5R'+Y8$-1+^D<2T_"W,QOA7@O[W7KXANT5]-<6G(47YYW*WS+;/=MT^,[&
XME,+#;K3J>%22"1@C\JT\93GRS7:_;!V86K7L#VA'=PKVPC$>8$_$!N$.8YIS
XMUYCIX'D'4^GKKIF].V:\PUQ9;)2.%7)2<G"DGD4G;!%<IZ5LP[F[ICM%\?3N
XM#)#HH7B4@\220H<B.E36GXTJ^7B,B.P3+2K)<0>'C(W!..1'4]?7<Q"@`QWA
XMY8R:9W9K`N=OL)GH$>UHDI"EW![XPV1D)1GEGGL,G;P%#Z:V^TDR[8)BO/43
XM8:7\/5SM`IFTZ2LMA>_F&IY7MD]T93&2.)2O((_NK`JT/S)[\9/M+J;!;5;(
XM9;.9+X\-M_D,<]ZC+4PEM#DFVMAI).7KM<>9/BD*_<_E4G;&\ER9;T=Z0.)Z
XM[W(X2D#F4A73UP!X4S.=H`=@LK.KB>2MN"V(<,*92BQ0%;=\Y[TE[\(Z>@R?
XM.I".M$&WN34%JQ6X;NW":H=^[GPSR)^9-4V?K&VPY"S8FC?[D/=7<IAQ&:/W
XM?M#R&!X&M>RZ6U7KNYIF2E/W!23M)DC@CL@]&T<OVSCK0>HNK0[V<`[W>FP^
XMI5N*C<X=S]`I"Y=H"(X<;T?#*"LX7>+@GB6L^+:#N3X<7Y5AI70.IM73_P"9
XM23(PX?I+C/W61]Q)Y#P'3P%-K1G9=8[(42I__LYZ1\;H]Q/X4_YZ5?DI2E(2
XMD!*0,``8`JG[I)4'NJG9]!M_U6VOCATB&ODJH:-[/-/Z<`>2S[;.(]^2^.(D
XM^0Z5<***OM:U@PT8"XN<7')11117TO$4445%$54^TS0=EUW9?8;FCNI+63%F
XM-I'>,*\O%.>:3L?(X-6RBO02#D+PC*X0[4-%7O1+DNVW9C!#:UQY"!EM]L9]
XMY)_<<Q5ZT'&C2[+;GX%NEW"2J,VI"Y1PPT"D<MR3CPVKIW5^F[1JNR.VB]14
XMR(S@.#]9M6,<23T/^'(KG&Z=BO:78Q_X]9+JU>-+\:BPTN08ZT))R4.8!XAO
XMZ59]L!&2!\7ZKQ_=)AKCH%\NNH+5"D\"W%:ENS?PLLD)B1SYD>[MY9.V^*U(
XM=JU;KZ:AI]*YC2593&8!;AL>OVL>))]:9V@NQ>/#8:=U&XTI2<'V2-LV#YGK
XM_G*FY;X42WQ4Q8,9J.PGDAM.!_W09\=15'\=V&^!_LJPP10_*,GR4M]$]D5K
XMMH:DWU:9[Z,%+"1PLH^76F8PRU'92RPTAIM`PE"$@`?*O2BK<4+(F]K!@+X>
X1]SSEQ111171?"****BB__]D`
X`
Xend
Xbegin 644 bg.jpg
XM_]C_X``02D9)1@`!`0$`2`!(``#_X0`617AI9@``34T`*@````@```````#_
XMVP!#``4#!`0$`P4$!`0%!04&!PP(!P<'!P\+"PD,$0\2$A$/$1$3%AP7$Q0:
XM%1$1&"$8&AT='Q\?$Q<B)"(>)!P>'Q[_VP!#`04%!0<&!PX("`X>%!$4'AX>
XM'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>'AX>
XM'A[_P``1"`)8`E@#`2(``A$!`Q$!_\0`&P`!`0$!`0$!`0````````````$"
XM`P0%!@C_Q``H$`$!``(#``,``P`#`0$!`0$``0(1`R$Q!!)!(E%A$S)Q,T)2
XM%('_Q``8`0$!`0$!`````````````````0(#!/_$`!L1`0$!`0$!`0$`````
XM```````!$0(2(3$#_]H`#`,!``(1`Q$`/P#^F@`````````````````#8`)I
XM4M043L4-;8Y.;'#JKS9?3';Y_)EER9LVK&^;DF=Z<?K:]&'%C)VU]<'.UTCS
XM\=^OKV</R<9_%SN.%CEGQZ[Q)4L?1EWVFNW@XN;+&ZR\>[CY,<L>O725FQHE
XM"-,B:V+^=`EZ#WU=:```````````````````````````````````````````
XM````````````````)!`5/S:R.7RN2<<9M6//\OEN4^L9X,=8[KGQ[SY-N^5F
XM,TY==-2)GE^,D(YVM*N.7XSH-7&L^*6;<<<\^++J=.LNF[]<YK7;<Z9QOAY_
XMOZ]$U>X^=GQ98W<K?!\GZ=5UG3-CW)/4PSF<VNV]9Q:!O8``````````````
XM``````````````````````````````````````````(L[$ML\`JQ/%G^FC/)
XMEJ;>#Y&=Y<NG;Y7+J_65QX,>[:Y]5J1TXY,<?]9SO:Y73.]N/5;BP(,*=[``
XM7SQ%UTNC4RWZSR\4R[QA&IE9XLZ2QQQRSX[WX]O#SX98ZOKC<,<_7#/"\>>\
XM77GIFQ]&=^*\?!SWJ9/7,L<IU765G%3:FHJ(L[)WZ>``":`(H`H`````````
XM`````````````````````````````````````BRZ$`9^1E,./;5NH\'/R7+/
XMZ[8M:CG;>3/;O9](G%C)CO\`4MM<K6I$MV3J!^,-18GABJ``@``L_H\1J?Z!
XMMJ:LU6;8-2F)GQ=;C'%RY<67?<=9?[,\,<YU&YTSCMQ<LS_79\RXY\=W*]'Q
XM_DR367KK.F<>JJSCE,O%_6I4Q0%9P`%`````````````````````````````
XM```````````````!-J`$*`GZK')9,:#E\WEF./\`%Y>/'[9?:IGE>3/7XZR?
XM7!QZK<AE==,;6]IIRM;59XDJU`18`!25``!85&@210`677B`-]93MQY>+5WB
XMW&YE^.DZ1QXN7+"ZKV<?+CE/>WGY.*9N/\N*[G;I.F;'TAYN#G^W5Z>F:OE=
XM)6<!+TJH````";4``````````````````````````````````````$UL\.XO
XM5@&M^!/\2[0+_'NO%\SEWEJ/3\GDDX[/UX<)<KNL]5J1OBQUW5ROXN5ZZ8_7
XM&UN&]$494GJH(*D6)`47\0!8@47]6II4`3:@`(+^(3U=&BXY:6R9>LZ)=-RC
XMGR<64[Q7A^1>/^.3KCEN]^,<W%CEWC'7GIFQZ^'.<C64U7S?MR\5_P`>OAYY
XME)N]NDK-CO+TA+OP:9771I.S=_07<3]6%```````````````````````````
XM````````````]32BH33.=^LW5O\`;S?,YOMC]8Q:U'#FSN7)_C6,U&./']K6
XM5TY=5N00O<(YK#8:78I"TGI065"""[Z2Y&5U''++=60=?LU*\_;6.6E\FNU$
XMQRVU6;`BI%0!-KZ``F!LI%V*GXUAE8A>UE1K+&<D[<,N.X7>+K]F\;+ZZ3I,
XM<N+Y&4NLNGLPSQRGKR<O#]^YTXX99<-[W77GIFQ]/>D]<OC\\Y.G:ZVUNLX>
XM`F_QI%`$`!0``````````````````````````````````U1+;,=IIC'-RSCF
XMJ\'>>=OXW\K/_DR,)]9TY]5N+YCIGU;>QRK:165GB!^E%$(?I/2HJ_B;D],?
XM&.2K(:SR9;\7@XKE=LXS[74>WX^'UQ[=N>6;TF?%+CU'EY.++&[>_P`\2XS/
XMJM^6/3Y\RL=,,_[;Y^#5_C'"2SURZY;E>CV$<L<[*ZS*6.5C:I.C:H@!`">K
XMHT"=K#8@:#:$&IE=_P"+GCAG/.V%G3<J8XY<>?'?M.HZ\/R.]9>NDOWFLO'+
XMEX).\72=)CV\>4SG2WKU\W#FY>++5\>[CY<<YW>W6=,6-_JFOZ\&D//1)WZU
XMJ:$0`4````````````````````````````````CS_*YM8W%UYK],?MMX.3+_
XM`),]L6M1./'=MK>]=$_C&=[KC:W%I#\(RJ+#HH@5)ZHI.EG8END@N74<,LMW
XM37)ELXN/[9.O,9KK\;CU9D]67?B828X:(]',<;2=+YV*WC.K['+EX9EVZ>-1
XMSO+4Z?.Y,+C4QRL?0Y./'*>/)R\%QNW/KAUG1CEMN//NRMX9.-C>NO2^,2M2
XML8"Q-+`2^BFP14-@IZBSL$O3>&3&KM?%T:SPG)/Z>;+'+BRW.W>95TWAE-6-
XMSIFQGX_R=ZQO3TRR_KP\O#EO[8IQ\MX[K)UG3-CZ`Y</+.3QUL;0$BJ@````
XM````````````````````````)+5U`$OA^N7R^3_CG26K'#Y7+<K<-N6&,D37
XMVR^S3CU6Y"W::!SU5Z06"HL3;0B%5-;%6,9V-6N/)>VI$3VO9\?"XS=<?C\?
XMV[KU6]:=^.7/KHOJXIC*ZXX;=7-C5OC6JZS&1G+*1+TRY_\`JRQC/+;%RTGI
XM->B%QF4U7"<MBSE[9MUJ7&>;@GY'DRPRPO;Z$SW&,L,>3USO+I.WCQR=<<HS
XMR\?UO3G+9>W.\NLNO1LVYX9-S7K%BJ+Z:9#06DH!>O$I`79.TJP$JSH/_%T;
XMF7Y6>3CQSFYZBXVXM2ICAKDXKN>/3P_(EDEO9=<DU7#EX?I_+'MUG28]^-^T
XMW%KP<'R,L;];.GLF>.4]=)=9L:#R;/\`6F5```30`4``````````````````
XM`0%J:_2K+KU*,YW6-KP<V?\`R9:_IV^7RZNH\^,[VQ:W&IU$JU'*M0`9*+4:
XM%C,:2%$5-Z(SG="Q.7+<Z8XY]LM)EV]/QN/K[.W'+/5=>+'Z8Z:QGVJ7NS3T
XM<6&IMVS'"W5PPU&X)?#1CESU'FRSMK7-EMR8M9J]LY&TM1%B[TR"M_9/OIBG
XM0K<SEZJ9\<R[C/ZLRL9L;G3E9]:N.6NZ[68V.66%W_C-CI.F\<I6G'>NF\<G
XM.QTE;)#<JQDJ+(E$1:;0T!272ZAT!H+44-6.F.<_>W/M=-2IB\G%,YO'IY]Y
XM<.7>Z]$RLOO3=QX\YVWSTEB\'R9R3ZNWCYW+QY<5^V/CT_&YIECJWMVE9L>G
XM6Q)N*TR````````````````````";4``$QWMCY.4QX]R]NENH^?\C.W.QGJK
XM&-W.[J[T8SI-]N-K<4!EH`0H;#0@"P%ZD<>3+;7)EKIQ[N73?/);CKP8??+5
XM>K_K/K&>+'Z82MR?:O3S,<>NG3AQWV],\<^''ZQTBUF#.?\`UK3')_U9H\>=
XMW4,YK)FL5FI1*1$79N@HEJ54#5BVT38:N]-\>7VNJY7M<+JF-2NG)Q?TY7&R
XMO=Q:RP<>7CC%Y=9TX2MRLY8:)ZY6-SIM8S%E9;6Z-Q/30A2>A$P70GZLH%ND
XMW:H:)^M3U$GJRE=>7^?'IY./^'/(]F/CR<G_`-W?BL5]'&[D5R^/WBZNT9H`
XM(````````````````(H!4M6))V!)5-]Z9Y,IAC6;5CC\SEDQU'CG=W5SRN>=
XMVOD<[6Y$I/2*YU03:Q%T``A0O0BQ,^IM9XYY9;NJLBLY7[.OQL)^QSX\;<NO
XM'KDF,Z>CB.7=-[OUCMP\;'!A_+;U34FG5R6=%$M8:+6,_%M<\ZN,VO/S=5RV
XMZ<UW7)BLT6$&45+4VEJJM3:'Z#6^DVEVG8UBW:\?_9G)KAEM4S'MX[K%C/,W
XMK%PSRVE36KE$L_8QC+:Z3S3ETZ\)&XSIJ5RKL5%J)``4"+:B!2!I1=F.-RNT
XMGNGHX<=3=61+3.S'!X9_/G=OF<G6I3XG%;K*QWYC->O"?3&-%_T=6```````
XM``````````````MZ"3:$36I]GC^7R[NH[_)Y9CC<7A[RNZQU6HN,_3?:7J$[
XM<];BTA_AXRI5B+"HF^U!!9_:7NGXE6!E=1SUNK;MTXL=7=;YC/5;XIK%J;M;
XMQX[E=SQZ>/CD]CT3XY7Z<6.L8WI?PT:2);IFW;59RZ6):SE7+DO36=<LNXM9
XMKA;ZB\DTS'/IFM2I:FTM80V(*T4B5!8ZX]K<6./UUGJ.D8N#?%-5:R:MC?)D
XMXWWI,K=M\?:6L>6L>HOJZT?ZY=5VYF`J1S=%OB`(;"19`1=&P"INE:PQVHUQ
XM8;NVOD<GTQU&NL,-O%S\EY,]1UYC-.'&\O)V^AQX_P#'CIR^/Q?3&9.^]NTC
XM%3U4BM(``````````````````"`+_P"IGE])NK[V\OR^7<^K-JQP^1G]L_\`
XM&69WZTY6ND2]A2,"E\1;X`>(H+*5(H$3(J3NK()AA;7KXL)9ISXY8]G#A-;=
XMN8Y=5OCPDC4(K;(EJ[8RJPI:YY5;7'DR;D8M9RRW6\,=N7^NW'>EK+A\J21P
XMCM\NN..G'HIMFTRNF;62+L9VNS%7:40-=,+IU][<)U%QSN]#<KOMG*Z3[:C%
XMMRNF6]U<9<K_`([XS3/%CJ-L=5J0J'M73E:Z0GB3U=$0#HI`2U8EA`-%-KKI
XM0DE=N.:[KGQ8[O;7/G],.FI$KC\KDN_K*?#XMW>4<N.7DY)7T<,)CC'?F,5J
XM=30#<2@"H``````````````````"%ZQVE6)S9S#%\W.VY[=ODYW*^N,<^JW(
XM:Z27;5_IF1SJK0$#?87T!8J&P5*1166L,-W:6.W%E)CJ^M\L=.G%_+IZ\.L=
XM/-\7"S*W\>JNT<Z0V);IK$+6,JMO3GGDU(6LYY.5[,LDC;G5G?3KQ]1SP_[.
XMF7^%''Y6/V>7*7%]'";]<^7BE\CE88\%NV7IRX:Y9<.48QF.<7:W"Q-=JU(G
XMK4Z-,VW\2H99?C?'C^F''^U<NO$72UTX<?Y;8PER>GCQTY]5TY^KKIFM5FN-
XMKO(1=I2(ILA811;`$"H"P-?JX]W23W3MACJ;6%:RUACMX>7*\F?U=?E<N^HG
XMQ>*W+[5VXY8UZ/B<7UP[=ZS+KJ-5UD8M#\!I`*(0`%`````````````G90``
XM''GY/K+'3ER^F&W@YL_O_)&^8Y\F7:3..7)EMG'_`-<Z[2/3O<V2N6.??U=9
XM)K;%B6%(;$9P#Q40A8LB;`BD2P%`GIICOP<MGKV89S*/G.G'R?6M\],6/=^,
XMVL8<FURO3OS=<^HF5Z<<\F\\NG"^NLCE:>KK^DVWAC^M$:QDD_U>]I?6I/UF
XMUJ36M_6+AGCE=;<.?/73EP[QS^VW/77Q\>GEPOVZB?\`';['HXK]L=M#G>7F
XM_P"#&^I__FP]>BZ9MG]KB//G\?&3IY\N'5\>]G/&67;-B5X+E]9IS_[5OY$U
XMETO!AOMRZN+(Z<.&G6W46>,9>N/5UWYYPM2+!AT*1+5A!*L3]/%"K#TO4`L0
XMG:XS=)!KCQ[VUS9S''2VS#!X^?.Y9.O/+-IQ8WDS?2XN.8X1Y/A68WM[I9?U
XMWG..=K&6/ZDMVZI<=M(S^'^LZNUE_$%"@H!H`3:@``````````3H"B02=;M6
XM./R>34Z1N1Q^1R;RL>3//^6FN7+??Z\]N[MFNO,=,\-S<<KTWCG_`&WEC,L>
XMO4='*?VZ89W]<]6=40L=Y=M.&.3MCE+&;&;&B](,XS8LIH\-HRL3:H@H`*LG
XM;*RK^#KA;BZXY[CA*LNO&N>L9ZY;Y-N<=)W$RG]/5QVX=<LR;KM.HSA-+>ZZ
XM6LR+.URR^N)-2.7-E^.7==OY\[7+/+[4PR[TS=)O7;EKT6?'T?CY?QTZUY/B
XMY;CTY?\`5TCS]./R<_K.GCG+E,][Z7Y%SN5WXYR;CM'&O9Q\TR3Y/+,<7DEN
XM$Z8SSRY+JN?=)]7"7DY-O7AA]8QP828R_KM;T\G5=N8ENF-[NBU)ZY.T*`H6
XM+"T@&RI1`\/5]2]*+.G3CQ_6./&VM<^<QQZ];D9M<OD\FOXO/.RVYY[K5DGC
XMU<<.5I]KCX[<//KUY_3QV\L6OJ<?+,HZRODX<N6/E>O@^1+_`-JS>5E>NZK%
XMPM\7#*9>-,8KEWCZUO;=DOKG99XBKK0DO]K_`.*'2?JB``*`````````0$Y+
XM,<7S^7/=NW?Y>7UNMO#S9,UTYC')EVR)6:[0L;PS^K,$:=OK,Y]OUSRQ9QMQ
XMKKC9G!7'6FL<FN3'3GX)8[X9.ENWFETZ8Y,V,6.NQ)VOC-C.$42U$_%2JFA%
XM-`"RZ;PK%1%=I=5K&]]N<RW&HUSWC'7+K?\`$QB8W7K>Y7?GMRO*9_\`5YL^
XMZ].<WCIY\YHZ=?Y_&/6=;NEO37'CNL<SZZ==3'?XLT]65ZTX83ZQTQNX[SEY
XMK6.7CF4>7+'ZWSI[;=QY?E<DDU^M7K'.QY^3+O4:^-Q[RW7+#&YY;>WBQU'F
XM[Z;YY:QFNOQG.]]-9WIRN[7GUWD4"(T`:`6(OA0HB^F"+)NFG3&:C4*LLPQK
XMQ<N=RRL=?DY_D<)-UWXY<^J0K7UNSZO7S,<>JFTJ_4L;837^IYY52])BQWX?
XMD98]/=Q<V.>/=?)]7#/+'+WIB\M2OLR[\5XOC_*GE>S'*6;VQ9C6IEBSWBZQ
XM+CM-&)=B7'2;1IH63<V@````````"9WZX[5S^1_\Q8\?R,_MMX\KNNV=]<;Z
XMRZ\H`CI`$OJ-:>K+KP$'7#*?_I,\-]SQSG;IAG?+X*YY=&-KKGA+.G*RP2NN
XM&>G2?R\>9KCSLZ2QFQZ)UZNF<;,EWIBQBQ?T/P93`!46(L\1%)=-XY,G[T#M
XM*NZY8VNF-AN,V.F-<N?'=W&_]A=6.GO4GQYM;KMC/I-ICA=K\C<PU';B,=5J
XM<N-Z;_\`'AXMW+M[,<M8]NUN..KR<GUQ?/Y,KGR-_(Y;;J'Q^/=W7#OIJ.W!
XMA)':]0QFHSE7FM=N8F59A2,ND4`!46`AI2H8:2FUDW5%PFSESF..FKK&;>7F
XMS^V3KQRQ:S;][VU)I,9IK;U<<N756^(([.-79=,J"5+I:S;I6BQ+-ERB?:*)
XMW/';@Y\\;_*].-L2]L6+'UN'Y&&4UOMZ)=OA89W"[CV_&^5;J9.=Y;U]"S;%
XMQ,,\<O*WMD<KN71*Z?7;&6.KT@4_"7KM/T50!0!``43QCY'_`,JW6/D?_*BQ
XM\KE]8];Y/:QXE=(GZ5:C-=)0-`T)M4L07PWN"?J*WAE]>JZ63..5T8Y644^M
XMF6BSIUW,YJ>N6>-QO8EAAE<7:9?::<)VLOUJ6:SCTX^*Y8<DO3IMFQFQ0&6<
XM`6"(H;0%E3TB*ZXY?C6MN.V\,M>GXSCI,M+=9QGJCMQVQURS_P`4EVY\_)]9
XMJ.N=LQ>'EMRR=;WKEY,,;ED]O%CK%RX./J5Z=S&//UTZ<\LY74<[VN5[3US=
XM9%@GBB@``!194JZ1($=,9J;9Q3ES^L=)$M8^1R=:CSX^F5WD;>G^?+EU727H
XMGK.*UZ)'&UHTS*LJLFDNV]PT*YLYS;K<6;QVB.'UO]EQ_P!=[Q9,_P#%DNM.
XM.O\`3>G3/'ZLZ56=GVOI8B7DUZ.#Y-PKZ'Q_D3DZO3Y$TN.>6-W*Q>5E??W/
XM[*^9\;Y?UU,J^AP\N/)-QRLQHN#-_C75,IL'/>U3+JDIBJ(J*``C'R/_`)5O
XM3//WQ4(^3G_VK%E=>3'NN=OX-RI?$D6D9;E0A0QJ4"B-:`(H0#6EQMEZ=)9E
XMZY3U?]0:RPO_`.7._P!5VPY/RG)A-;@CE.G7CS_MRUH$L>N67P>?#.QVQREC
XMG8S8VG9%]98L/PD/PE$4$%4VA$&\<G3&N+6.08Z98[<LN*;=95ZV>F+#&?6,
XM97MK.].>]U&I"^DT5%59ZK+2";4`%B""PUL]:Q]:D2TNL<>WEY,[E>W3Y&??
XM3SVO1_/EBT)ZBQZ.9CEU6Q-GK>N:QCDRU>FI[IG/&6J+CE:ZXW;CC-.O'K8.
XMDCIA#&?JW+&3U!OZ_P!IE>.37ZXY<]\D9F'W_E;HQ=9YL+EWB\^4L]>O+.<<
XMU.WFSR^U:@YVFJU<9.V=WR1=$JS'++J.W%P99WN/=\?XDQ[KGUU&Y'D^/\;*
XMV6Q]+@XYQS4=,<9C%KE:H;3;-R,$Y/[<>3/7AS<F4ZTY<>\\NP>G#O';43&:
XMQ76XB@D!53*?::4!\[YF'UKRZ[?5^1Q_>6OF<N-QRHLK%2-6=,P:E%L*1,;E
XM0/T1K0(5&@!,70`4;X\OK?Y=L0177*3+N.=GU7'+7_C>IE!'+QK#+59SQL)`
XMQZ<,]M[_`*>29:KMAEOUBQFQU]32RQ6?QBQ(J;-HBHJ2(*0VL!9=+]F:1,1;
XM=H4%&D+X"-)%!-JD4`#](-8Q.7+46V8XO-RY[KMSRQ:QGE]JS2U'IXYQRZH2
XM@ZL5N"8KM<3"U&<JS+:#KZF[C=IVUCC:([8<V\/K^L?3*W>UQDQ3//\`H5O<
XMQG;GER_D<[E;4Z_1&K;;VEJXXW*]/1P_&RO_`&B6K(X8<66=>OX_Q-=UZN+A
XMQQDZ=9TYWIN1G##'&>-]2)TSEEJ,_JM,Y9R.67+)UMRRSM!USY%PRVX8RVN_
XM'CKL%SQE8DF-=,JY<N4V#M+N#/'WBTC18)=@+:)5@)_CS?+X)9N/3?5NK.P?
XM%Y,<L;K3,T^E\G@W-R/G<F%QR\%UGQ:4_!J4V5(5+&I1:(F-R@NT1J&P$4`%
XMT697'I#6P=L=9SMC/'7C,RN+I,IE-(KEHF5C>6#%Z$QVPS_MUQNWDE=,,M,V
XM,V/0:3'*6*QC%BG0(A2`@M(FU``0%V@`U&5T"I5@"1K23U.3+4;YB6L\V76G
XMGM7.]L6O3QRY=4HD*[R.=IL0\6(NT^U3>ZW^*,R;;QQN_#&.LSF,$)A/TRRF
XM,Z8Y.7;E;:HZW/;&TFW7BX;G?$M,<]6^._!PW._RCT\'QICW7IQDG4C-Z:QR
XMXOC8X>/1.IK20MTYVM1:GVU^N>?)IQRY=HKMR<LGCAER6LR7)O'!49F-K>.#
XMICBU=0&9C(W'*Y]MX7:8,\V64\<<9<LNWJRD<[)C5'3&:Q7V$[Q&5A*%$5*L
XMZ(50O9XD6T0ZOKS_`"?CS*6R.]67K5%?%Y..\=NV+NOK\_!CG-Z?-Y>++#*]
XM=`YQ*?JT:E/Q/U94_4QN4HM32-2@+ZC4J`#0`BZGI+92>J@WCE_;6>/V\<;6
XM\,[/02]=([98XV;GKE<;*"XY65WPSECSW_#'*RI8S8]@Y89].DKG8S8NU]2P
XMC+)HB@```""Z5-H&M"2-19#2]1Y^7/MOES_'GRNZ[\<L6I;M-?B^,W+MZ>8Y
XM6KHU4N2?;)MA;9&;=U9-WM=2*)CC6XDJ=[$JVFSHF.5O26F,UTX^*Y>1VX?C
XMV^Q[>/BQQGC.M2//P_&U_P!H]>&&,G44C-K6+%W)&<L\9'#DY?Z0=LN33CR<
XMN_'.Y6KCCL$_E:U,6\<'222=@SABW=1C/.3QSN>5!TRS8N5ICC:Z8X0&<<;7
XM7":7J("YY3UY^7DF5Z:Y=L<6&[V@]/'_`-)LO?BR:QTGB+#L-@J@`"*";5*L
XM\`CES<4Y)ITB_@/D_(X+AEU'!]KDX\<YK3Y_R?CW&]*FO-$__2ZU=)^C4JTA
XM2(W*E#?:U&Y4+!?QEJ(`+IX%$5-->LQ=HL7'.XUUZSCD8VRBKEA]:R[2S*,9
XM8_H,3+5=N/-QL)N%C-CV8Y;:V\V&>G?#+<<[&+%V%(PR+#8`"^((L]+4BP:C
XM.66FKU'GY,MNG/+-J9W=8I4>GGERM,^XQ]6QUC&LS!>DM9NU1JUGVGXN,O\`
XM1JK)I>[^-\?%ED]7#P3'NQ-,<.+@^WKU\7!,73'&3R-2,VKACJ?C26R.6?+(
XMRKK<I'//DUXXY\GV\9DRH+EE<C'%O'!UQQUZ#&/'MTF,AEE(Y99[Z@.F6<CG
XM>2U)+:WA@#,QM=,,&I)"Y3'M!9C(992.6?)OQGNJ-7.[=>+MC#!VQDB:)R8Q
XMSPLE;Y<I,:\6'//OV:N/=+M=)A9<(J:N)K8":*`H``````)GA,YVJ;T)CY_R
XMOCV=R/)9];JOMY8S*=O%\KXT[RBZKPTQ,I<?88]C4J?JIKM;XC4J:%A4;E2>
XM!^#+6@`LH0$Q386)/15EL=<,I^N27:#KGCN[C%FF^/+K5:RQF4V*XMX9V5FR
XMQ!+'JPSW'25XL<K*]''GMF\L6.FFOQF6#G8RH0907PGJ9W4TWS$K'+FX95K*
XMN?Z]''+GU5/_`%+TFW>1RM6W^F;:MJ>M,B::F-WU'?BX=^HTXX<>5KU<7#_<
XM=L.*8QTD9M6)AA)XW(>,Y92?K.JW>F,N21QSY6-W(&\^6WQCO*]KCQNV.$!S
XMQP=<,--36,8SY=>`W<L<8YY<G],96Y+AQH&[6Y@UC@WN2`F&,GJW*1C+DUXY
XMW*Y*.F6;G;E5QPM=<<.@<\,+7;'"+O'&=N/+S3'RLVDCM;,8X<OR).I7EY?D
XM99=.?>5VQ>FY';/FN76W+*?6[:QQ_:SR7?1*N/;\7DW)'I>#XO5>Z>-)5H@N
XM(H```F@`H```!4NK-6':]`\?R?C?;^4>#/&X76GVKWU^//\`*^/,IO&*FOF;
XM/8WS<=PZ<YX+*8E]6(C<J_B+^$1N5!:B-2@"-:EJSL!2PB=JBQ:8Y_6H(.TU
XMR1SSQ^J3*X^.DLRG8.4GZLR^K66-G<8#'?BY.^W>66/#+KQTPSLO;-C-CU'_
XM`/U,,I8NF/+FMZFW#DRW6^7/K4<,KJ:=>>6+6<J>1(NNGIYCC:AI8UCQY6M(
XMQ)77CX;D[<?'/V.V.&O&=7&./CF/KK,?Z7'%;9BFKBR%RDCEGS8N.>>65Z0=
XM>3GGCE;<JF&%M[=L<`<\<+7;##34QTF6>,#6I)&<N3ZN>6=OB3&WT%N5SNUF
XM%K>''IO&2`SC@Z:DC&6<D<LL[?`=<N21RN5I,;;VZ8X`QCC:Z8\;<QD3/FQQ
XMB6JW-8SMSY.:8QY>;Y&_*\]RSROO3%Z7'?FY[EXX7[6^KCC:W))ZQ:UC.,;U
XM,9M+E&>ZBM99[FHQCC;73#CM>GBX9_3<B)\?#3U3J,X8ZK=;9J"BZR`(T```
XM`````````X?)X,<L=SU\[EXLL+X^Q/7+GXIGB#X\6NO/PWCOCC5%_$G:_P#Y
XM24K4I5T4_&6Y4!:RW*@`U*`"@"*++I`P=)EOI,\/_P"6-MX<FND-8O0Z9X];
XMCF)6\.2RO1_R=/))VZ>1J1SZ:SO>W+*[JY7:8XVNDCA:N,:^M_'3CXW?'#3H
XMPX\?%^UWPPU&L<6Y(S:N,XXM]3UC/DF$<,^6Y(._)R2>5PRY,LF9+;ZZ8X`Y
XMS'==<,&\,/[;NL8"3&1;EC'+/EVQ=Y`UGR7?7B27+UK#!VQP@.6'&[3&2%RF
XM,<L^3L'2YR.6?)?QF[R;PP!B2Y7MTQP;F,:MQQF]I:ICB7/''VN'-\GZ]1Y>
XM3EO)XS>C'HYOD7_\UYLL\L_4QQN^V],6M2,3!TQQAN1FY6UEIJW7C-MK6.%R
XM=N/A:Q''#CM>CCX?\=N/CD=9)&I$<\..1TDTHTB*`````````````````)5G
XM04&.7CQSGCYWR/CW"V_CZ<_U.3"9SL'Q?W2:>OY/!<;N1Y,I95#*:7?1372-
XM2H4GJU,;E06)4QJ4"%1N4`%`$$T5:1!O#+7K.?=W&:UC_K4B6M8>=E[:QQWX
XMZX<<_6I''JN6/':[\?'KUO''^FYC6XY5)(UC%WC)VX\O+_\`S51URSF+CR<G
XM].=RRR7'"WU!-W)O'!TX^-O4GH,X\;<LP]8SY)/'*Y99`ZY\T\CE;EE?5QPV
XM[8X0'/##;KAQZ7K%G/DDG5!NV8L9\L_'*Y7(QPV!;EE6\,+^MX8.FIC.T5G'
XM%JZQG;GR<V.,]>/EY\KY6;T8]7)SXSJ/+R\URO5<_P"67K>.$8O34C$QRR]=
XM,,-+UBF>77\65QJV1FY;\3''+*]NV'#N]1J17+'"VN^'#N.W%PZ]CMCC(U(F
XMN7'Q2>NTDBI=M8FJA%_!``````````````````````$I+OI0$SQF4T\/ROC_
XM`%NY'O+)9JD1\//&R]D?0^9\;<W(\%QN.6JJLWU<NC],NS&I4_#_`-6727UF
XMM2FA?8GB-R@43&@6:2B@!A:3NMR;NF?'HX.*W^34CEUTUPX:CM,6I)(QGRR-
XM.-K?6+GGS:ZCCGR7+Q,<;?50RRN57'&NF/&Z8XR`QAQ_KK-1,LYC''/E^W@.
XMN><Q\<LN2Y),+73#C!C'&UTPXW3'"2)EG,0:F,D8SSF/CGER7+QF8VU!J\ER
XM28VNF'&ZXXR`QAQ].DDC.7),(\_+\F>1+5QZ,^3'&=5Y.7Y-MTXVY97U<<7.
XM]-8EMSNVL<6YCHN3+1)$N6O$[R=,..U9!SDN3KQ\3OAPNV.$C<B:Y8<,=\,)
XM(NM*TS:;`7$`$4```````````````````````````]`O\NJ\WR>#&S<G;TSI
XM+-@^-R<>6&7;/_CZO/P8Y8V_KYO+Q986]*,2)UM94U_(65<B?Z4B-RI0_59;
XMU!4T+H0IC:L9M=N#&6]O3]\<<=1Y<;KQKUK'&UO/DM\8DN36&#MAAI67/#!U
XMQPTO\8QGRZZ@.ERQQCEGR?TY[N5[;QXP8GVRO;KCQQO##4;WC`9PQ:RRQQCE
XMGRV>.>\LZ#>7);XS-Y7MK#!UQPT#GCQNV&,TN\9.ZX<O-,?^M2U<=LLIBX<O
XMR,=:E>?/GRRZ8F.[NN=Z7%SY,\KZ8X[;QPGM:ZDZ8U4F.EMDB7+*^-887)9%
XM8W:WAA<G;CX?\>CCXI&YRFN''P7^GHPXY/72="XFIJ?B@J``H`F@```````H
XM``````````````````````&]`!K?;ES<,Y)TZ_\`@J/D<_#<+7*73[/+QXYX
XM^=OF_)^/<<MP'"]D(8^C6I^JE6>)C4H!4:UFUKCGVK&3T_$DUVU&>JN.#KAQ
XMM;D+RXR*Y-221C+EUTY99Y6])CC;>Q5RRN2X86^MX8.LD@C..#<DC.7)(XY9
XMV^`Z\G+JZCE;<C'"WUUX\->@QAA;ZZXX:;U(QGS8XS2:KI))'+EYI@\W+S7*
XM_P`:Y?RRO=8O2R-\G-<[TQJWUK'!TDDC&U<8QP;U(EO]$EIFJ7+?1CA:[<?%
XMOUZ,.+7XU.1PXN/^X]&'#KMUF,D)MK$TQFIH4-0E-(*+O0``````````````
XM"``H````````````````````$`#SQGDPQSG;1KH1\WY?Q[C=XSIY/+V^WE)9
XMJQX?E_%U_**/&L2XW'U9V*(U&;-F-:YW_MT[<5LG3CYD]/%-B59EE?5F.W3#
XMC=,>.16&,.-TF,GIEE,8Y9\EH.F6>,\KEGR9;U&9+E77#C#7.2Y7MUPX_P!=
XM)A(9YXXSU%)CKM,N3#&>]N'+\F_]8\^65SK-Z7'?EY[^.%RRSO:XX.DQD8M7
XM&,<&Y)"W1-U/UHM)+6\..VO3Q\*R(\_'Q6O3Q\,GXZXX2-_C<2LXX2-`J`2`
XM````````````````````````````````````````````%Z@'Z!ZEDOJW_`1X
XM_E?'W+9'@RQN%?;LEFJ\WR/CRRV0'SM[2]-<F&6&7;/L:BN6?_;;U?%RFNWD
XMY>J[?%O2I7M_YL9^,Y<NYTYW';6&"(S?M:WCQNF.#?6/J:N,XXS35UC-N7+S
XM82=5Y<^7.W4\9M,>GD^1/'ESSRROI,=^MXX:8O3<C&.-M[=)AIKJ1G=OB?:J
XM[TG=K>.%KMQ\-_I9$URPX[7?CX7?#CF,[C<FFY$UC#"3\=)I%,30!0``````
XM`````````````````````````````````````````!%`"=``&^M!;V#A\GX\
XMSFWS^3CN%\?7UKURY>*9SI94?%Y9WLX,_K=/3\KAN/X\5WCDUH^KQ27';?\`
XM&?KP\/R-336?+<O*S3'IY.68SJO+R<^65TQW?UO'%BUIB2WUTQP:UI-_C*KJ
XM0W;TN.%KMQ\6S!QF%KOQ\+OAQ21UQQTU(FN>'%(ZR20HTB7M0```````````
XM`````````````````````````````````````````````!/U;JB`3?ZM_P`+
XMV3T'/DXYE.X^?\KXM]D?4M[Z2XS*=KH_/Y\>6-7#+7KZW-\;'+R/+G\75\0>
XM><F+?WE\=9\6?TZ<?Q9/QG%UQPQRKOQ\6W;CX=.^.$BX:Y<?%KV.LQD\:%0\
XM`0$5-$%`4```````````````````````````````````````````````````
XM`````19>@!-?JWOH!#P`4C.4EO@`?2+)`$5/T$54GH"AK8-(+OH$`!(`"@``
X4```````````````````````#_]D`
X`
Xend
