#!/usr/bin/perl -w

#
# Copyright (C) 2003 dan sinclair
#
# The license for this program is the GPL v2 or greater.
# A copy of the GPL should have been distributed with
# this program.
#

$| = 1;

use strict;
use HTML::TreeBuilder;
use Getopt::Std;
use CSS::Tiny;

my $fop_home = "/usr/local/fop-0.20.5rc/";
my $java_home = "/opt/blackdown-jdk-1.3.1/";

####################################################
##### Probably don't need to edit below here #######
####################################################
my $version = "0.6.5";

my %options = ();
getopts('dlwesfhpti:c:', \%options);

&usage if ($options{"h"});
my $debug = $options{"d"} || 0;		
my $image_dir = $options{"i"} || "";
my $footer = $options{"f"} || "";
my $header = $options{"e"} || "";
my $warnings = $options{"w"} || "";
my $css_dir = $options{"c"} || "";
my $padding = $options{"p"} || 0;
my $do_svg = $options{"s"} || 0;
my $test_only = $options{"t"} || 0;

my $fop = $fop_home . "fop.sh";

my $in_file = shift or &usage;
my $out_file = shift or &usage;

# the style sheet stuff
my $css = CSS::Tiny->new();

# page width settings, the numbers are in millimeters
my $full_page_width = "215";
my $full_page_height = "279";
my $page_margin = "10.7";

my $error_count = 0;
my $ignore_next_text = 0;

# this is the size of a tab
my $tab_is = "        ";

# flip numbers if we are in landscape mode
if ($options{"l"}) {
    my $t = $full_page_width;
    $full_page_width = $full_page_height;
    $full_page_height = $t;
}

# amount of page we can use
my $page_width = $full_page_width - (2 * $page_margin);

# this is a stack of current table data
my @table_data = ();
my $table_count = 0;

# a stack of the current td cell we are in
my @cur_td = ();

# keep track of the used targets in <a> tags
my %used_a_targets = ();

# html colour name -> value
my %html_colours = ("black"   => "#000000", "green"  => "#008000",
                    "silver"  => "#c0c0c0", "lime"   => "#00ff00",
                    "gray"    => "#808080", "olive"  => "#808000",
                    "white"   => "#ffffff", "yellow" => "#ffff00",
                    "maroon"  => "#800000", "navy"   => "#000080",
                    "red"     => "#ff0000", "blue"   => "#0000ff",
                    "purple"  => "#800080", "teal"   => "#008080",
                    "fuchsia" => "#ff00ff", "aqua"   => "#00ffff");

# the html tag -> subroutine mapping
my %html_funcs = (  "~comment"   => \&html_comment,     "a"          => \&html_a,
                    "address"    => \&html_address,     "b"          => \&html_b,
                    "big"        => \&html_big,         "blockquote" => \&html_blockquote,
                    "body"       => \&html_body,        "br"         => \&html_br,
                    "center"     => \&html_center,      "cite"       => \&html_cite,
                    "code"       => \&html_code,        "del"        => \&html_del,
                    "dd"         => \&html_dd,          "div"        => \&html_div,
                    "dl"         => \&html_dl,          "dt"         => \&html_dt,
                    "em"         => \&html_em,          "font"       => \&html_font,
                    "form"       => \&html_form,        "h1"         => \&html_h,
                    "h2"         => \&html_h,           "h3"         => \&html_h,
                    "h4"         => \&html_h,           "h5"         => \&html_h,
                    "h6"         => \&html_h,           "head"       => \&html_head,
                    "hr"         => \&html_hr,          "html"       => \&html_html,
                    "i"          => \&html_i,           "img"        => \&html_img,
                    "input"      => \&html_input,       "ins"        => \&html_ins,
                    "kbd"        => \&html_kbd,         "li"         => \&html_li,
                    "link"       => \&html_link,        "nobr"       => \&html_nobr,
                    "ol"         => \&html_ol,          "option"     => \&html_option,
                    "p"          => \&html_p,           "pre"        => \&html_pre,
                    "q"          => \&html_q,           "s"          => \&html_s,
                    "samp"       => \&html_samp,        "script"     => \&html_script,
                    "select"     => \&html_select,
                    "small"      => \&html_small,       "span"       => \&html_span,
                    "strike"     => \&html_strike,      "strong"     => \&html_strong,
                    "sub"        => \&html_sub,         "sup"        => \&html_sup,
                    "table"      => \&html_table,       "td"         => \&html_td,
                    "textarea"   => \&html_textarea,    "title"      => \&html_title,
                    "tr"         => \&html_tr,          "tt"         => \&html_tt,
                    "u"          => \&html_u,           "ul"         => \&html_ul,
                    "var"        => \&html_var
                 );

# stores formating data for the page
my %formatting_data = ();

# the default font size
my $base_font_size = 11;
format_push("font-size", $base_font_size);

# the title of the html document
my $title = "";
my $next_is_title = 0;

# build the html tree
my $tree = &get_html_tree;
open(OUT, ">$out_file.$$") or die "Can't open temporary $out_file.$$";

&print_fo_header;
walk($tree, "");
&print_fo_footer;

close(OUT);

# cleanup the tree
$tree = $tree->delete;

exit(1) if ($test_only);

if (!$error_count) {
    # setup java and pass file to fop
    $ENV{"JAVA_HOME"} = $java_home;
    if ($debug) {
        system("$fop -d $out_file.$$ -pdf $out_file");
    } else {
        system("$fop -q $out_file.$$ -pdf $out_file");
    }
} else {
    print "Encountered ($error_count) errors, not running fop\n";
}
unlink("$out_file.$$") if (!$debug);

###################################################
#####              Sub routines              ######
###################################################

###################################################
## print error messages
sub error {
    my $err = shift;
    print "Error: $err\n";
    $error_count ++;
}

###################################################
## print error messages, kill program afterwards
sub error_death {
    my $err = shift;
    print "Error: $err\n";
    print "Continuing is not a good idea, exiting.\n";
    exit(-1);
}

###################################################
## print warning messages
sub warning {
    my $warn = shift;
    print "Warning: $warn\n" if (($warnings) || ($debug));
}

###################################################
## print the program usage stuff
sub usage {
    print <<"    __HERE";

denature $version

Usage: denature <options> <input.html> <output.pdf>
 <options>
 -i <dir>   - specify a directory for images used in HTML file.
 -c <dir>   - specify a directory for any css files used in the HTML file.
 -l         - work in landscape mode.
 -s         - turn on SVG graphics (requires a DISPLAY and a valid X server).
 -f         - print a footer with the page number in it.
 -e         - print a header with the title in it.
 -p         - turn on handling of CSS padding. Can cause strange output.
 -h         - print simple help text.
 -d         - turn on debug output.

    __HERE
    exit(0);
}

################################################
## build and return the html tree from the 
## input file
sub get_html_tree {
    my $tree = HTML::TreeBuilder->new;
    $tree->implicit_tags(1);
    $tree->store_comments(1);
 
    open(IN, "$in_file") or die "Can't open input file ($in_file)";
    my @lines = <IN>;
    close(IN);

    foreach my $l (@lines) {
        # FOP doesn't like what HTML::TreeBuilder turns the 
        # &nbsp; into, so pre convert to a space
        $l =~ s/\&nbsp;/<!-- SPACE -->/g;   
        $l = clean_tabs($l);
        $tree->parse($l);
    }
    $tree->eof;
    $tree->elementify();

    return $tree;
}

################################################
## this cleans up tabs
sub clean_tabs {
    my $l = shift;
    my $tab_size = length($tab_is);
    my @line = split(//, $l);

    $l = "";
    foreach my $element (@line) {
        if ($element =~ /\t/) {
            my $num_space = $tab_size - (length($l) % $tab_size);
            for(my $i = 0; $i < $num_space; $i ++) {
                $l .= " ";
            }
        } else {
            $l .= $element;
        }
    }
    return $l;
}

################################################
## print the header for the fo document
sub print_fo_header {
    print OUT <<"    __HERE";
<?xml version="1.0"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
 <fo:layout-master-set>
  <fo:simple-page-master master-name="first"
       page-width="${full_page_width}mm" page-height="${full_page_height}mm"
       margin-top="0mm" margin-bottom="0mm"
       margin-left="${page_margin}mm" margin-right="${page_margin}mm">
   <fo:region-after extent="0.5in" />
   <fo:region-before extent="0.5in" />
    __HERE
}

################################################
## print the footer for the fo document
sub print_fo_footer {
    print OUT <<"    __HERE";
 </fo:page-sequence>
</fo:root>
    __HERE
}

###############################################
## walk the html tree, at each node print
## any required fo stuff
sub walk {
    my $node = shift;
    my $indent = shift;

    debug_print_node($node, $indent);

    if (ref($node) ne "HTML::Element") {
        if ($next_is_title && $header) {
            $title = $node;
            $next_is_title = 0;
        } else {
            if (!$ignore_next_text) {
                print_fo_inline($node);
            } else {
                $ignore_next_text = 0;
            }
        }
    } else {
        # don't walk the title if we have no header
        return if(($node->tag eq "title") && !$header);

        if (process_open($node)) {
            foreach my $child ($node->content_list) {
                walk($child, $indent . " | ");
            }
        }
        process_close($node);
    }
    debug_print_node($node, $indent . "/");
}

##############################################
## print the info for this node
sub debug_print_node {
    return if (!$debug);
    my $node = shift;
    my $indent = shift;

    if (ref($node) ne "HTML::Element") {
        print $indent . $node . "\n"; 
    } else {
        print $indent . $node->tag . "\n";
    }
}

##############################################
## handle an open tag
sub process_open {
    my $node = shift;
    my $tag = lc($node->tag);

    handle_css($node) if ($node->attr("class"));
    
    if (!defined($html_funcs{$tag})) {
        warning("Unknown tag, " . $tag);
        return 0;
    }
    return $html_funcs{$tag}->($node, "handle");
}

#############################################
## handle a close tag
sub process_close {
    my $node = shift;
    my $tag = $node->tag;

    return if (!defined($html_funcs{$tag}));
    $html_funcs{$tag}->($node, "antihandle");
    unhandle_css($node) if ($node->attr("class"));
}

#############################################
## get the background colour add push it
## onto the formatting stack
sub get_node_colour {
    my $node = shift;
    my $attr = shift;
    my $type = shift;

    my $colour = $node->attr($attr);

    $colour = $html_colours{$colour} if ($colour !~ /^#/);

    # keep the length to # + 6 chars
    $colour = substr($colour, 0, 7) if (length($colour) > 7);

    error("Unknown colour spec (" . $node->attr($attr) . ")") 
            if (!defined($colour) || ($colour eq ""));
    format_push($type, $colour) 
}

#############################################
## handle a html script tag 
sub html_script {
    # ignore everything inside of a script
    return 0;
}

#############################################
## handle a html q (quote) tag 
sub html_q {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        my $inquote = format_get_top("inside-q-element") || "\'";
        my $q = "\"";
        $q = "'" if ($inquote eq "\"");
        print_fo_inline($q);
        format_push("inside-q-element", $q);
    } else {
        my $q = format_get_top("inside-q-element");
        format_pop("inside-q-element");
        print_fo_inline($q);
    }
}

#############################################
## handle a html table tag 
sub html_table {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        $table_count ++;
        print_fo_table_start($node);
    } else {
        &print_fo_table_end;
        pop @table_data;
    }
    return 1;
}

#############################################
## handle a html tr tag 
sub html_tr {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        push @cur_td, -1;
        get_node_colour($node, "bgcolor", "background-color") if ($node->attr("bgcolor"));
        print_fo_tr_start($node);
    } else {
        format_pop("background-color") if ($node->attr("bgcolor"));
        &print_fo_tr_end;
        pop @cur_td;
    }
    return 1;
}

#############################################
## handle a html td tag 
sub html_td {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        $cur_td[$#cur_td] ++;
        if ($node->attr("colspan")) {
            format_push("colspan-stack", $node->attr("colspan"));
        } else {
            format_push("colspan-stack", 1);
        }

        if ($node->attr("align")) {
            my $val = lc($node->attr("align"));
            $val = "center" if ($val eq "middle");
            format_push("alignment", $val);
        }
        get_node_colour($node, "bgcolor", "background-color") if ($node->attr("bgcolor"));
        print_fo_td_start($node);
        &print_fo_block_start;
    } else {
        &print_fo_block_end;
        &print_fo_td_end;
        format_pop("background-color") if ($node->attr("bgcolor"));
        format_pop("alignment") if ($node->attr("align"));
        format_pop("colspan-stack");
    }
    return 1;
}

#############################################
## handle a html b tag 
sub html_b {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-weight", "bold")
    } else {
        format_pop("font-weight");
    }
    return 1;
}

#############################################
## handle a html strong tag 
sub html_strong {
    return html_b(@_);
}

#############################################
## handle a html i tag 
sub html_i {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-style", "italic");
    } else {
        format_pop("font-style");
    }
    return 1;
}

#############################################
## handle a html em tag 
sub html_em {
    return html_i(@_);
}

#############################################
## handle a html u tag 
sub html_u {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("text-decoration", "underline");
    } else {
        format_pop("text-decoration");
    }
    return 1;
}

#############################################
## handle a html strike tag 
sub html_strike {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("text-decoration", "line-through");
    } else {
        format_pop("text-decoration");
    }
    return 1;
}

#############################################
## handle a html ins tag 
sub html_ins {
    html_u(@_);
}

#############################################
## handle a html del tag 
sub html_del {
    html_strike(@_);
}

#############################################
## handle a html s tag 
sub html_s {
    return html_strike(@_);
}

#############################################
## handle a html tt tag 
sub html_tt {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-family", "monospace");
    } else {
        format_pop("font-family");
    }
    return 1;
}

#############################################
## handle a html big tag 
sub html_big {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-size", format_get_top("font-size") + 3);
    } else {
        format_pop("font-size");
    }
    return 1;
}

#############################################
## handle a html small tag 
sub html_small {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-size", format_get_top("font-size") - 3);
    } else {
        format_pop("font-size");
    }
    return 1;
}

#############################################
## handle a html center tag 
sub html_center {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("alignment", "center");
    } else {
        format_pop("alignment");
    }
    return 1;
}

#############################################
## handle a html sup tag 
sub html_sup {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("vertical-align", "super");
        format_push("font-size", format_get_top("font-size") * 0.75);
    } else {
        format_pop("vertical-align");
        format_pop("font-size");
    }
    return 1;
}

#############################################
## handle a html sub tag 
sub html_sub {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("vertical-align", "sub");
        format_push("font-size", format_get_top("font-size") * 0.75);
    } else {
        format_pop("vertical-align");
        format_pop("font-size");
    }
    return 1;
}

#############################################
## handle a html br tag 
sub html_br {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("linefeed-treatment", "preserve");
        format_push("white-space-collapse", "false");
        format_push("white-space-treatment", "preserve");
        &print_fo_inline("\n");
        format_pop("linefeed-treatment");
        format_pop("white-space-collapse");
        format_pop("white-space-treatment");
    }
    return 1;
}

#############################################
## handle a html font tag 
sub html_font {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        if ($node->attr("size")) {
            my $size = $node->attr("size");
            if ($size =~ /([0-9]+)pt$/) {
                format_push("font-size", $1);

            } elsif ($size =~ /^[\-+]([0-9]*)/) {
                format_push("font-size", format_get_top("font-size") + $1);
            }
        }

        get_node_colour($node, "color", "color") if ($node->attr("color"));
    } else {
        format_pop("font-size") if ($node->attr("size"));
        format_pop("color") if ($node->attr("color"));
    }
    return 1;
}

#############################################
## handle a html img tag 
sub html_img {
    my $node = shift;
    my $type = shift;

    print_fo_img($node) if ($type eq "handle");
    return 1;
}

#############################################
## handle a html pre tag 
sub html_pre {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("linefeed-treatment", "preserve");
        format_push("white-space-collapse", "false");
        format_push("white-space-treatment", "preserve");
        format_push("font-family", "monospace");
    } else {
        format_pop("linefeed-treatment");
        format_pop("white-space-collapse");
        format_pop("white-space-treatment");
        format_pop("font-family");
    } 
    return 1;
}

#############################################
## handle a html title tag 
sub html_title {
    my $node = shift;
    my $type = shift;

    $next_is_title = 1 if ($type eq "handle");
    return 1;
}

#############################################
## handle a html body tag 
sub html_body {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        get_node_colour($node, "bgcolor", "background-color") if ($node->attr("bgcolor"));
        &print_body_stuff;
        format_pop("background-color");

        print_fo_flow_start("xsl-region-body");
        &print_fo_block_start;
    } else {
        &print_fo_block_end;
        &print_fo_flow_end;
    }
    return 1;
}

#############################################
## handle a html comment tag 
sub html_comment {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        if ($node->attr("text") =~ /PAGEBREAK/) {
            format_push("break-before", "page");
            &print_fo_block_empty if (!@table_data );

        } elsif ($node->attr("text") =~ /SPACE/) {
            format_push("linefeed-treatment", "preserve");
            format_push("white-space-collapse", "false");
            format_push("white-space-treatment", "preserve");
            print_fo_inline(" ");
            format_pop("linefeed-treatment");
            format_pop("white-space-collapse");
            format_pop("white-space-treatment");
        }
    }
    return 1;
}

#############################################
## handle a html input tag 
sub html_input {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        my $style = $node->attr("class") || "";

        # input type == text
        print_fo_inline($node->attr("value")) if ($node->attr("type") =~ /text/i);

        # input type == checkbox, selected == false
        print_instream_foreign_object(get_svg(10, 10, get_svg_box(10, 10))) 
                if ($do_svg && (($node->attr("type") =~ /checkbox/i) && (!$node->attr("checked"))));

        # input type == checkbox, selected == true
        print_instream_foreign_object(get_svg(10, 10, get_svg_box(10, 10, get_svg_check_mark(10, 10)))) 
                if ($do_svg && (($node->attr("type") =~ /checkbox/i) && ($node->attr("checked"))));

        # input type == radio, selected == false
        print_instream_foreign_object(get_svg(10, 10, get_svg_circle(10, 10, 4, 0))) 
                if ($do_svg && (($node->attr("type") =~ /radio/i) && (!$node->attr("checked"))));

        # input type == radio, selected == true
        print_instream_foreign_object(get_svg(10, 10, 
                        get_svg_circle(10, 10, 4, 0) . get_svg_circle(10, 10, 2, 1))) 
                if ($do_svg && (($node->attr("type") =~ /radio/i) && ($node->attr("checked"))));

        # 9pt font size
        my $len = length(($node->attr("value") || ""));
        my $w = $len * 5.6;     # this number is made up, but it appears to look right for my test doc...

        # input type == button
        my $colour = get_css_value($node->tag, $style, "background");
        $colour = "white" if (!defined($colour));
        print_instream_foreign_object(get_svg($w, 15, get_svg_box($w, 15, 
                            get_svg_text($w, 15, $node->attr("value"), 
                                get_css_value($node->tag, $style, "font-size"),
                                get_css_value($node->tag, $style, "font-family"),
                                get_css_value($node->tag, $style, "font-weight"),
                                get_css_value($node->tag, $style, "color")
                            ), 1, $colour))) 
                if ($do_svg && ($node->attr("type") =~ /button/i));

        # input type == img
        print_fo_img($node) if ($node->attr("type") =~ /image/i);
    }
    return 1;
}

#############################################
## handle a html textarea tag 
sub html_textarea {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("linefeed-treatment", "preserve");
        format_push("white-space-collapse", "false");
        format_push("white-space-treatment", "preserve");
    } else {
        format_pop("linefeed-treatment");
        format_pop("white-space-collapse");
        format_pop("white-space-treatment");
    }
    return 1;
}

#############################################
## handle a html a tag 
sub html_a {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        print_basic_link_open($node);
    } else {
        print_basic_link_end($node);
    }
    return 1;
}

#############################################
## handle a html h[1-6] tag 
sub html_h {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        my %h_data = (h1 => $base_font_size * 2,
                      h2 => $base_font_size,
                      h3 =>  6,
                      h4 =>  2,
                      h5 => -2,
                      h6 => -4);

        format_push("font-size", $base_font_size + $h_data{$node->tag});
        format_push("font-weight", "bold");
        if ($node->attr("align")) {
            my $val = lc($node->attr("align"));
            $val = "center" if ($val eq "middle");
            format_push("alignment", $val);
        }
        &print_fo_block_start;
    } else {
        format_pop("font-size");
        format_pop("font-weight");
        format_pop("alignment");
        &print_fo_block_end;
    }
    return 1;
}

#############################################
## handle a html hr tag 
sub html_hr {
    my $node = shift;
    my $type = shift;

    print_fo_leader($node) if ($type eq "handle");
    return 1;
}

#############################################
## handle a html kbd tag 
sub html_kbd {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-weight", "bold");
        format_push("font-family", "monospace");
        format_push("font-size", format_get_top("font-size") + 2);
    } else {
        format_pop("font-weight");
        format_pop("font-family");
        format_pop("font-size");
    }
    return 1;
}
   
#############################################
## handle a html address tag 
sub html_address {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        &print_fo_block_start;
        format_push("font-style", "italic");
    } else {
        format_pop("font-style");
        &print_fo_block_end;
    }
    return 1;
}

#############################################
## handle a html blockquote tag 
sub html_blockquote {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("start-indent", "1.5cm");
        format_push("end-indent", "1.5cm");
        &print_fo_block_start;
    } else {
        format_pop("start-indent");
        format_pop("end-indent");
        &print_fo_block_end;
    }
    return 1;
}

#############################################
## handle a html cite tag 
sub html_cite {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-style", "italic");
    } else {
        format_pop("font-style");
    }
    return 1;
}

#############################################
## handle a html code tag 
sub html_code {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-family", "monospace");
    } else {
        format_pop("font-family");
    }
    return 1;
}

#############################################
## handle a html dt tag 
sub html_dt {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-weight", "bold");
        format_push("keep-with-next", "always");
        &print_fo_block_start;
        format_pop("keep-with-next");
    } else {
        format_pop("font-weight");
        &print_fo_block_end;
    }
    return 1;
}

#############################################
## handle a html dd tag 
sub html_dd {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("start-indent", "1cm");
        &print_fo_block_start;
    } else {
        &print_fo_block_end;
        format_pop("start-indent");
    }
    return 1;
}

#############################################
## handle a html nobr tag 
sub html_nobr {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        warning("nobr will allow text to run off the side of your pdf, and hence become invisible.");
        format_push("wrap-option", "no-wrap");
    } else {
        format_pop("wrap-option");
    }
    return 1;
}

#############################################
## handle a html p tag 
sub html_p {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        &print_fo_block_start;
    } else {
        &print_fo_block_end;
    }
    return 1;
}

#############################################
## handle a html swap tag 
sub html_samp {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-family", "monospace");
        format_push("font-size", format_get_top("font-size") + 2);
    } else {
        format_pop("font-family");
        format_pop("font-size");
    }
    return 1;
}

#############################################
## handle a html var tag 
sub html_var {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        format_push("font-family", "monospace");
        format_push("font-style", "italic");
    } else {
        format_pop("font-family");
        format_pop("font-style");
    }
    return 1;
}

#############################################
## handle a html div tag 
sub html_div {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        if ($node->attr("align")) {
            my $val = lc($node->attr("align"));
            $val = "center" if ($val eq "middle");
            format_push("alignment", $val);
        }
        get_node_colour($node, "bgcolor", "background-color") if ($node->attr("bgcolor"));
        &print_fo_block_start;
    } else {
        &print_fo_block_end;
        format_pop("alignment") if ($node->attr("align"));
        format_pop("background-color") if ($node->attr("bgcolor"));
    }
    return 1;
}

#############################################
## handle a html select tag 
sub html_select {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        return 0 if (($node->attr("size")) && ($node->attr("size") > 1));

        my $val = "";
        foreach my $child ($node->content_list) {
            $val = check_option($child);
            last if ($val ne ""); 
        }
        print_fo_inline($val);
        print_instream_foreign_object(get_svg(10, 10, get_svg_box(10, 10, 
                            get_svg_arrow_down(10, 10, 4), 0))) if ($do_svg); 
    }
    return 0;
}

#############################################
## gets the value of the first option element
## inside of a select element
sub check_option {
    my $node = shift;
    return "" if ($node->tag !~ /option/i);

    if ($node->attr("selected")) {
        my $ret = "";
        foreach my $child ($node->content_list) {
            if (ref($child) ne "HTML::Element") {
                $ret = $child;
                last;
            }
        }
        return $ret;
    }
    return "";
}

#############################################
## handle a html option tag 
## this is probably never called as it should
## be handled by the select code above
sub html_option {
    my $node = shift;
    my $type = shift;

    $ignore_next_text = 1 if ($type eq "handle");
    return 1;
}

#############################################
## handle a html link tag 
sub html_link {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        if (-e $css_dir . "/" . $node->attr("href")) {
            $css = CSS::Tiny->read("$css_dir/" . $node->attr("href")) 
                    if ($node->attr("rel") =~ /stylesheet/i);
            error_death(CSS::Tiny->errstr() . 
                "\nNote, you need a space after the : in your CSS statements.\n") 
                if (!defined($css));

            # make all the keys lower case
            foreach my $key (sort keys %{ $css }) {
                $css->{lc($key)} = $css->{$key};
            }
        }
    }
    return 1;
}

#############################################
## handle a html html tag 
sub html_html {
    my $node = shift;
    my $type = shift;
    return 1;
}

#############################################
## handle a html head tag 
sub html_head {
    my $node = shift;
    my $type = shift;
    return 1;
}

#############################################
## handle a html form tag 
sub html_form {
    my $node = shift;
    my $type = shift;
    return 1;
}

#############################################
## handle a html dl tag 
sub html_dl {
    my $node = shift;
    my $type = shift;
    return 1;
}

#############################################
## handle a html span tag 
sub html_span {
    my $node = shift;
    my $type = shift;
    return 1;
}

#############################################
## handle a html ul tag 
sub html_ul {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        my $type = "disc";
        $type = $node->attr("type") if ($node->attr("type"));

        &print_fo_list_block_start;
        format_push("unordered-list-symbol", $type);
    } else {
        format_pop("unordered-list-symbol");
        &print_fo_list_block_end;
        html_br($node, "handle");
    }
    return 1;
}

#############################################
## handle a html ol tag 
sub html_ol {
    # XXX this is wrong, but keep it from not working
    # by pretending that ol == ul for now...
    html_ul(@_);
}

#############################################
## handle a html li tag 
sub html_li {
    my $node = shift;
    my $type = shift;

    if ($type eq "handle") {
        &print_fo_list_item_start;
        &print_fo_list_item_label_start;

        &print_fo_block_start;
        my $type = format_get_top("unordered-list-symbol");
        my $inner = "";
        if ($type eq "circle") {
            $inner = get_svg_circle(8, 8, 3, 0);
        } elsif ($type eq "disc") {
            $inner = get_svg_circle(8, 8, 3, 1);
        } else {
            $inner = get_svg_box(8, 8, "", 1);
        }
        print_instream_foreign_object(get_svg(8, 8, $inner)) if ($do_svg);  

        &print_fo_block_end;
        &print_fo_list_item_label_end;

        &print_fo_list_item_body_start;
        &print_fo_block_start;
    } else {
        &print_fo_block_end;
        &print_fo_list_item_body_end;
        &print_fo_list_item_end;
    }
    return 1;
}

##############################################
## print the rest of the header and the start
## of the body code
sub print_body_stuff {
    my $bg = "";
    my @back = @{ format_get("background-color") };
    $bg = "background-color=\"" . $back[$#back] . "\"" if (@back);

    print OUT <<"    __HERE";
       <fo:region-body margin-bottom="0.5in" margin-top="0.5in" $bg/>
      </fo:simple-page-master>
     </fo:layout-master-set>

     <fo:page-sequence master-reference="first" language="en" hyphenate="true" initial-page-number="1">
    __HERE

    if ($footer ne "") {
        print OUT <<"        __HERE";
         <fo:static-content flow-name="xsl-region-after"> 
          <fo:block text-align="center">
           p. <fo:page-number />
          </fo:block>
         </fo:static-content>
        __HERE
    }

    if ($header ne "") {
        print OUT << "        __HERE";
         <fo:static-content flow-name="xsl-region-before">
          <fo:block text-align="center">$title</fo:block>
         </fo:static-content>
        __HERE
    }
}

##############################################
## print a fo:inline element
sub print_fo_inline {
    my $text = shift;
    $text =~ s/&/&amp;/g;

    my $align = format_get_top("alignment");
    my $size = format_get_top("font-size");
    my $weight = format_get_top("font-weight");
    my $styles = format_get_top("font-style");
    my $lf = format_get_top("linefeed-treatment");
    my $wsc = format_get_top("white-space-collapse");
    my $wst = format_get_top("white-space-treatment");
    my $bb = format_get_top("break-before");
    my $col = format_get_top("color");
    my $family = format_get_top("font-family");
    my @deco = @{ format_get("text-decoration") };
    my $wrap = format_get_top("wrap-option");
    my $vert_align = format_get_top("vertical-align");
    my $padd_left = format_get_top("padding-left");
    my $padd_right = format_get_top("padding-right");
    my $padd_top = format_get_top("padding-top");
    my $padd_bottom = format_get_top("padding-bottom");

    # this will do the multple text-decoration stuff when fop supports it
#   if (@deco) {
#       my $dec_content = "";
#       foreach my $d (@deco) {
#           $dec_content .= " " if ($dec_content ne ""); 
#           $dec_content .= "$d";
#       }
#       $decoration = "text-decoration=\"$dec_content\"";
#   }
    warning("fop does not handle mulitple text-decorations") if (@deco > 1);

    &print_fo_block_empty if ($bb);
    print OUT "<fo:inline";
    print OUT " text-align=\"" . $align . "\"" if ($align);
    print OUT " font-size=\"" . $size . "pt\"" if ($size);
    print OUT " font-weight=\"" . $weight . "\"" if ($weight);
    print OUT " font-style=\"" . $styles . "\"" if ($styles);
    print OUT " linefeed-treatment=\"" . $lf . "\"" if ($lf);
    print OUT " white-space-collapse=\"" . $wsc . "\"" if ($wsc);
    print OUT " white-space-treatment=\"" . $wst . "\"" if ($wst);
    print OUT " color=\"" . $col . "\"" if $col;
    print OUT " font-family=\"" . $family . "\"" if ($family);
    print OUT " wrap-option=\"". $wrap . "\"" if ($wrap);
    print OUT " vertical-align=\"" . $vert_align . "\"" if ($vert_align);
    print OUT " text-decoration=\"" . $deco[$#deco]  . "\"" if (@deco);

    if ($padding) {
        print OUT " padding-top=\"" . $padd_top . "\"" if ($padd_top);
        print OUT " padding-bottom=\"" . $padd_bottom . "\"" if ($padd_bottom);
        print OUT " padding-left=\"" . $padd_left . "\"" if ($padd_left);
        print OUT " padding-right=\"" . $padd_right . "\"" if($padd_right);
    }

    print OUT ">";

    # this gets rid of any character > ascii 126 and converts it to a '?'
    # fop keeps complaining about: [ERROR] Invalid byte 1 of 1-byte UTF-8 sequence.
    my @text = split(//, $text);
    foreach my $c (@text) {
        $c = ((ord($c) > 126) ? "?" : $c);
    }
    $text = join('', @text);

    print OUT $text;
    print OUT "</fo:inline>\n";
}

##############################################
## print a fo:leader element
sub print_fo_leader {
    my $node = shift;

    my $thickness = 1;
    my $length = "100%";

    $thickness = $node->attr("size") if ($node->attr("size"));
    $length = $node->attr("width") if ($node->attr("width"));
    $length .= "pt" if ($length !~ /%$/);

    if ($node->attr("align")) {
        my $val = lc($node->attr("align"));
        $val = "center" if ($val eq "middle");
        format_push("alignment", $val);
    }
    &print_fo_block_start;
    print OUT "<fo:leader leader-pattern=\"rule\" rule-thickness=\"${thickness}pt\" "; 
    print OUT "leader-length=\"$length\" rule-style=\"solid\" />\n";
    &print_fo_block_end;
    format_pop("alignment") if ($node->attr("align"));
}

##############################################
## print a fo:instream-foreign-object element
sub print_instream_foreign_object {
    my $content = shift;

    print OUT "<fo:instream-foreign-object>\n";
    print OUT $content;
    print OUT "</fo:instream-foreign-object>\n";
}   

#############################################
## print a fo:list-item start
sub print_fo_list_item_start {
    print OUT "<fo:list-item>\n";
}

#############################################
## print a fo:list-item end
sub print_fo_list_item_end {
    print OUT "</fo:list-item>\n";
}

#############################################
## print a fo:list-block
sub print_fo_list_block_start {
    print OUT "<fo:list-block>";
}

#############################################
## print a fo:list-block end
sub print_fo_list_block_end {
    print OUT "</fo:list-block>";
}

#############################################
## print a fo:list-item-label start
sub print_fo_list_item_label_start {
    print OUT "<fo:list-item-label end-indent=\"label-end()\">\n";
}

#############################################
## print a fo:list-item-label end
sub print_fo_list_item_label_end {
    print OUT "</fo:list-item-label>\n";
}

#############################################
## print a fo:list-item-body start
sub print_fo_list_item_body_start {
    print OUT "<fo:list-item-body start-indent=\"body-start()\">\n";
}

#############################################
## print a fo:list-item-body end
sub print_fo_list_item_body_end {
    print OUT "</fo:list-item-body>\n";
}

##############################################
## print a fo:table element
sub print_fo_table_start {
    my $node = shift;
   
    my %table = ();
    $table{"border-width"} = $node->attr("border") || undef;
    $table{"padding-width"} = $node->attr("cellpadding") || undef;
    $table{"width"} = $node->attr("width") || "100%";

    print OUT "<fo:table table-layout=\"fixed\">\n";
    $table{"num_columns"} = calc_columns_in_table($node);
    $table{"cell-widths"} = get_column_widths($node, $table{"width"});
    print OUT "<fo:table-body>\n";

    push @table_data, \%table;
}

##############################################
## print a fo:table end element
sub print_fo_table_end {
    print OUT "</fo:table-body>\n";
    print OUT "</fo:table>\n";
}

##############################################
## print a fo:table-row element
sub print_fo_tr_start {
    my $node = shift;
    my $rowspan = "";
    $rowspan = "number-rows-spanned=\"" . $node->attr("rowspan") . "\"" if ($node->attr("rowspan"));
    print OUT "<fo:table-row $rowspan>\n"
}

##############################################
## print a fo:table-row ending element
sub print_fo_tr_end {
    print OUT "</fo:table-row>\n";
}

##############################################
## print a fo:table-cell element
sub print_fo_td_start {
    my $node = shift;

    my %table = %{ $table_data[$#table_data] };
    my $back = format_get_top("background-color");

    print OUT "<fo:table-cell";
    if ($table{"padding-width"}) {
        my $amt = $table{"padding-width"};
        print OUT " padding-top=\"" . $amt . "pt\"";
        print OUT " padding-bottom=\"" . $amt . "pt\"";
        print OUT " padding-left=\"" . $amt . "pt\"";
        print OUT " padding-right=\"" . $amt . "pt\"";
    }

    print OUT " number-columns-spanned=\"" . $node->attr("colspan") . "\"" if ($node->attr("colspan"));
    print OUT " background-color=\"" . $back . "\"" if ($back);

    if ($table{"border-width"}) {
        print OUT " border-width=\"" . $table{"border-width"} . "pt\"";
        print OUT " border-style=\"solid\"";
    }
    print OUT ">\n";
}

##############################################
## print a fo:table-cell ending element
sub print_fo_td_end {
    print OUT "</fo:table-cell>\n";
}

##############################################
## print a fo:external-graphic element
sub print_fo_img {
    my $node = shift;

    my $width = "";
    my $height = "";
    my $src = $node->attr("src");

    $src = $image_dir . "/" . $src if ($src !~ /^\./);
    $width = $node->attr("width") if ($node->attr("width"));
    $height = $node->attr("height") if ($node->attr("height"));

    print OUT "<fo:inline>\n";
    print OUT "<fo:external-graphic";
    print OUT " height=\"" . $height . "px\"" if ($height ne "");
    print OUT " width=\"" . $width . "px\"" if ($width ne "");
    print OUT " src=\"" . $src . "\" />\n";
    print OUT "</fo:inline>\n";
}

##############################################
## print an external link
sub print_basic_link_open {
    my $node = shift;

    my $target = $node->attr("href") || "";
    my $name = $node->attr("name") || "";

    if (($name ne "") && (defined($used_a_targets{$name}))) {
        warning("Warning: Two <a names> with the name: $name\n");
   
        # makeup a new name.... 
        my $old_name = $name;
        for(my $i = 0; ;$i ++) {
            $name = $old_name . $i;
            last if (!defined($used_a_targets{$name}));
        }
    }
    $used_a_targets{$name} = 1;

    my $link_type = "external-destination";
    $link_type = "internal-destination" if ($target =~ /^#/);
    $target = $1 if ($target =~ /^#(.*)/);

    print OUT "<fo:inline id=\"$name\">\n" if ($name ne "");
    print OUT "<fo:basic-link $link_type=\"$target\">" if ($target ne "");
}

##############################################
## print an empty fo:block
sub print_basic_link_end {
    my $node = shift;

    my $target = $node->attr("href") || "";
    my $name = $node->attr("name") || "";

    print OUT "</fo:basic-link>\n" if ($target ne "");
    print OUT "</fo:inline>\n" if ($name ne "");
}

##############################################
## print an empty fo:block
sub print_fo_block_empty {
    &print_fo_block_start;
    &print_fo_block_end;
}

##############################################
## print the opening if a fo:block
sub print_fo_block_start {
    my $f_size = $base_font_size;
    my $bb = format_get_top("break-before");
    my $bg = format_get_top("background-color");
    my $alignment = format_get_top("alignment");
    my $font_size = format_get_top("font-size");
    my $s_indent = format_get_top("start-indent");
    my $e_indent = format_get_top("end-indent");
    my $keep_with = format_get_top("keep-with-next");
    my $padd_left = format_get_top("padding-left");
    my $padd_right = format_get_top("padding-right");
    my $padd_top = format_get_top("padding-top");
    my $padd_bottom = format_get_top("padding-bottom");

    $f_size = $font_size if ($font_size);
    print OUT "<fo:block";
    print OUT " break-before=\"" . $bb . "\"" if ($bb);
    print OUT " background-color=\"" . $bg . "\"" if ($bg);
    print OUT " text-align=\"" . $alignment . "\"" if ($alignment);
    print OUT " start-indent=\"" . $s_indent . "\"" if ($s_indent);
    print OUT " end-indent=\"" . $e_indent . "\"" if ($e_indent);
    print OUT " keep-with-next=\"" . $keep_with . "\"" if ($keep_with);

    if ($padding) {
        print OUT " padding-top=\"" . $padd_top . "\"" if ($padd_top);
        print OUT " padding-bottom=\"" . $padd_bottom . "\"" if ($padd_bottom);
        print OUT " padding-left=\"" . $padd_left . "\"" if ($padd_left);
        print OUT " padding-right=\"" . $padd_right . "\"" if($padd_right);
    }
    print OUT " line-height=\"" . ($f_size + 4) . "pt\"";
    print OUT ">\n";

    # page breaks are only valid for one block
    format_pop("break-before") if ($bb);
}

##############################################
## print the ending of a fo:block
sub print_fo_block_end {
    print OUT "</fo:block>\n";
}

##############################################
## prints the start of a fo:flow
sub print_fo_flow_start {
    my $region = shift;
    my $col = format_get_top("background-color");

    print OUT "<fo:flow flow-name=\"$region\"";
    print OUT " background-color=\"" . $col . "\"" if ($col);
    print OUT ">\n";
}

##############################################
## prints the ending of a fo:flow
sub print_fo_flow_end {
    print OUT "</fo:flow>\n";
}

##############################################
## returns the widths of the columns in this table
sub get_column_widths {
    my $node = shift;
    my $table_width = shift;
    my @max_widths = ();

    my $cols_in_table = calc_columns_in_table($node);

    # init sizes to -1
    for(my $i = 0; $i < $cols_in_table; $i++) {
        push @max_widths, -1;
    }

    # get the widths for all rows
    foreach my $row ($node->content_list) { 
        next if (lc($row->tag) ne "tr");

        my @widths = @{ get_widths_in_row($row) };
        next if (!@widths);

        error_death("Columns in row (" . @widths . ") don't match columns in table ($cols_in_table). "
              . "Table ($table_count)") if (@widths != $cols_in_table);
        for(my $i = 0; $i < $cols_in_table; $i++) {
            # this needs to be fixed (how?)...will always take the first rows
            # widths if their set
            $max_widths[$i] = $widths[$i] if (($max_widths[$i] eq -1) && ($widths[$i] ne -1));
        }
    }

    # get the width we have to work in
    my $p_width = $page_width;
    if (@table_data > 0) {
        my %t = %{ $table_data[$#table_data] };
        my @c = @{ $t{"cell-widths"} };
        $p_width = 0;
        my @cspan_stack = @{ format_get("colspan-stack") };
        for(my $i = 0; $i < $cspan_stack[$#cspan_stack]; $i++) {
            $p_width += $c[$cur_td[$#cur_td]];
            # only increment if the next is gunna come through here...
            $cur_td[$#cur_td]++ if (($i + 1) < $cspan_stack[$#cspan_stack]);
        }
    }

    # if the table has a width specified, integrate it
    if ($table_width =~ /([0-9]+)%/) {
        $p_width = ($p_width * ($1 / 100));
    } elsif ($table_width < $p_width) {
        $p_width = $table_width;
    }

    my $num_unspec = 0;
    my $remaining_width = $p_width;

    # each of the specified widths will behandled in this for
    for(my $i = 0; $i < $cols_in_table; $i ++)  {
        if ($max_widths[$i] eq -1) {
            $num_unspec++;
            next;
        }
        $max_widths[$i] = ($p_width * ($1 / 100)) if ($max_widths[$i] =~ /([0-9]+)%/);
        $remaining_width -= $max_widths[$i];
    }

    # if the width left to work with is less then 0 we got a problem...
    if ($remaining_width < 0) {
        print "Error: Table widths calculated don't fit ($remaining_width) table ($table_count)\n";
        $remaining_width = 1;
    }

    if ($num_unspec > 0) {
        my $generic_width = $remaining_width / $num_unspec;
        # all widths that are -1's become the generic width
        for(my $i = 0; $i < $cols_in_table; $i++) {
            $max_widths[$i] = $generic_width if ($max_widths[$i] eq -1);
        }
    }

    # print out the fo stuf
    for(my $i = 0; $i < $cols_in_table; $i++) {
        print OUT "<fo:table-column column-width=\"" . $max_widths[$i] . "mm\" />\n";
    }
    return \@max_widths;
}

##############################################
## get the widths of the datacells in this row
sub get_widths_in_row {
    my $row = shift;
    my @widths = ();

    foreach my $col ($row->content_list) {
        next if (lc($col->tag) ne "td");
        next if ($col->implicit());

        if ($col->attr("colspan")) {
            my $spanned = $col->attr("colspan");
            for(my $i = 0; $i < $spanned; $i ++) {
                push @widths, -1;
            }
        } elsif ($col->attr("width")) {
            push @widths, $col->attr("width");
        } else {
            push @widths, -1;
        }
    }

    return \@widths;
}

##############################################
## returns the number of columns in this table
sub calc_columns_in_table {
    my $node = shift;

    # get the first child of the node
    my @children = $node->content_list;
    my $child = $children[0];

    my $columns = 0;
    foreach my $cols ($child->content_list) {
        next if (lc($cols->tag) ne "td");

        if ($cols->attr("colspan")) {
            $columns += $cols->attr("colspan");
        } else {
            $columns ++;
        }
    }
    return $columns;
}

##############################################
## simple css handling for elements
sub get_css_value {
    my $type = shift;
    my $style = shift;
    my $val = shift;

    my $ret = undef;
    if ($css->{"$type.$style"}->{$val}) {
        $ret = $css->{"$type.$style"}->{$val};
    } elsif ($css->{".$style"}->{$val}) {
        $ret = $css->{".$style"}->{$val};
    } elsif ($css->{"$type"}->{$val}) {
        $ret = $css->{"$type"}->{$val};
    }
    return $ret;
}

##############################################
## simple css handling for elements
sub handle_css {
    my $node = shift;

    my $type = lc($node->tag);
    my $style = $node->attr("class");

    format_push("background-color", get_css_value($type, $style, "background")) 
                if (defined(get_css_value($type, $style, "background")));
    format_push("color", get_css_value($type, $style, "color")) 
                if (defined(get_css_value($type, $style, "color")));
    format_push("font-weight", get_css_value($type, $style, "font-weight")) 
                if (defined(get_css_value($type, $style, "font-weight")));

    if (defined(get_css_value($type, $style, "font-size"))) {
        my $s = get_css_value($type, $style, "font-size");
        $s =~ s/pt$//g;
        $s =~ s/px$//g;
        format_push("font-size", $s);
    }

    if (defined(get_css_value($type, $style, "text-align"))) {
        my $val = lc(get_css_value($type, $style, "text-align"));
        $val = "center" if ($val eq "middle");
        format_push("alignment", $val);
    }

    format_push("text-decoration", get_css_value($type, $style, "text-decoration")) 
                if (defined(get_css_value($type, $style, "text-decoration")));
    format_push("vertical-align", get_css_value($type, $style, "vertical_align")) 
                if (defined(get_css_value($type, $style, "vertical-align")));

    my $do_pad = get_css_value($type, $style, "padding");
    if (defined($do_pad)) {
        my @p = split(/\s+/, $do_pad);
        
        my $padding_top = undef;
        my $padding_right = undef;
        my $padding_bottom = undef;
        my $padding_left = undef;
        
        if (@p == 1) {
            $padding_top = $p[0];
            $padding_bottom = $p[0];
            $padding_left = $p[0];
            $padding_right = $p[0];
        } elsif (@p == 2)  {
            $padding_top = $p[0];
            $padding_bottom = $p[0];
            $padding_left = $p[1];
            $padding_right = $p[1];
        } elsif (@p == 3) {
            $padding_top = $p[0];
            $padding_left = $p[1];
            $padding_right = $p[1];
            $padding_bottom = $p[2];
        } else {
            $padding_top = $p[0];
            $padding_right = $p[1];
            $padding_bottom = $p[2];
            $padding_left = $p[3];
        }
        format_push("padding-top", $padding_top);
        format_push("padding-right", $padding_right);
        format_push("padding-bottom", $padding_bottom);
        format_push("padding-left", $padding_left);
    }
}

##############################################
## simple css anti-handling for elements
sub unhandle_css {
    my $node = shift;

    my $type = $node->tag;
    my $style = $node->attr("class");

    format_pop("background-color") if (defined(get_css_value($type, $style, "background")));
    format_pop("color") if (defined(get_css_value($type, $style, "color")));
    format_pop("font-weight") if (defined(get_css_value($type, $style, "font-weight")));
    format_pop("font-size") if (defined(get_css_value($type, $style, "font-size")));
    format_pop("text-align") if (defined(get_css_value($type, $style, "text-align")));
    format_pop("text-decoration") if (defined(get_css_value($type, $style, "text-decoration")));
    format_pop("vertical-align") if (defined(get_css_value($type, $style, "vertical-align")));

    my $do_unpad = get_css_value($type, $style, "padding");
    if (defined($do_unpad)) {
        format_pop("padding-left");
        format_pop("padding-right");
        format_pop("padding-top");
        format_pop("padding-bottom");
    }
}

##############################################
## return the svg header code
sub get_svg {
    my $width = shift;
    my $height = shift;
    my $content = shift;

    my $svg = "<svg:svg width=\"" . $width . "pt\" height=\"" . $height . "pt\" ";
    $svg .= "xmlns:svg=\"http://www.w3.org/2000/svg\">\n";
    $svg .= $content;
    $svg .= "</svg:svg>\n";
    return $svg;
}

##############################################
## return the svg code for a text element
sub get_svg_text {
    my $width = shift;
    my $height = shift;
    my $content = shift;
    my $size = shift || "9pt";
    my $family = shift || "verdena";
    my $weight = shift || "bold";
    my $color = shift || "#000";

    my $h = $height - 2;
    my $text = "<svg:text x=\"5pt\" y=\"${h}pt\" font-size=\"$size\" font-family=\"$family\" ";
    $text .= "fill=\"$color\" font-weight=\"$weight\">";
    $text .= $content;
    $text .= "</svg:text>\n";
    return $text;
}

##############################################
## return the svg code for a box
sub get_svg_box {
    my $width = shift;
    my $height = shift;
    my $content = shift || "";
    my $fill_var = shift || 0;
    my $fill_color = shift || "black";

    my $fill = "fill:none;";
    $fill = "fill:$fill_color;" if ($fill_var == 1);

    my $box .= "<svg:rect style=\"$fill stroke:$fill_color\" x=\"1pt\" y=\"1pt\" ";
    $box .= "width=\"" . ($width - 2) . "pt\" height=\"" . ($height - 2) . "pt\" />\n";
    $box .= $content;
    return $box;
}

##############################################
## return the svg code for a circle
sub get_svg_circle {
    my $width = shift;
    my $height = shift;
    my $radius = shift;
    my $fill = shift;

    my $x_center = ($width / 2);
    my $y_center = ($height / 2);
    my $style = "fill: none; stroke: black; stroke-width: 0pt";
    $style = "fill: black" if ($fill == 1);

    my $circle = "<svg:circle cx=\"${x_center}pt\" cy=\"${y_center}pt\" r=\"${radius}pt\" ";
    $circle .= "style=\"$style\" />";
    return $circle;
}

##############################################
## return the svg code for a box
sub get_svg_check_mark {
    my $width = shift;
    my $height = shift;

    my $start = "M" . ((1/4) * $width) . ", " . ((1/2) * $height);
    my $movement = "L" . ((1/2) * $width) . ", " . ((5/8) * $height);
    $movement .= " " . ((3/4) * $width) . ", " . ((1/4) * $height);

    my $mark = "<svg:path d=\"$start $movement\" ";
    $mark .= "style=\"fill: none; stroke:black; stroke-width:2\" />";
    return $mark;
}

##############################################
## return the svg code for a down arrow
sub get_svg_arrow_down {
    my $width = shift;
    my $height = shift;
    my $size = shift;

    my $start_w = ($width - $size) / 2;
    my $start_h = ($height - $size) / 2;

    my $path = "$start_w, $start_h, " . ($start_w + $size) . ", $start_h, ";
    $path .= ($start_w + ($size / 2)) . ", " . ($start_h + $size - 1) . ", $start_w, $start_h";

    my $arrow = "<svg:polygon points=\"$path\" ";
    $arrow .= "style=\"fill: black; stroke:black; stroke-width:2\" />";
    return $arrow;
}


##############################################
## add value to the element entry in the 
## formatting_data
sub format_push {
    my $element = shift;
    my $value = shift;

    my $ref = $formatting_data{"$element"};
    my @data = ();
    @data = @{ $ref } if ($ref);

    push @data, $value;
    $formatting_data{"$element"} = \@data;
}

##############################################
## get the value for the entry in the 
## formatting_data with element name
sub format_get {
    my $element = shift;
    my @d = ();
    return $formatting_data{"$element"} || \@d;
}

##############################################
## get the top element for the entry in the 
## formatting_data with element name
sub format_get_top {
    my $element = shift;
    my @d = @{ format_get($element) };
    return undef if (!@d);
    return $d[$#d];
}

##############################################
## pop the end element off the entry in the 
## formatting_data
sub format_pop {
    my $element = shift;
    my $ref = $formatting_data{"$element"};
    return if (!$ref);

    my @data = @{ $ref };
    pop @data;
    $formatting_data{"$element"} = \@data;
}

__END__

=head1 NAME

denature -- A script to convert an html file to an xsl-fo file, then using FOP, to a pdf.

=head1 SYNOPSIS

B<denature> [B<-i dir>] [B<-c dir>] [B<-l>] [B<-s>] [B<-f>] [B<-e>] [B<-w>] [B<-d>] [B<-p>] [B<-t>] [B<-h>] <input file> <output file>

=head1 DESCRIPTION

I<denature> will parse through the given HTML file and produce a roughly equilavent
PDF file. I<denature> should be able to recognize most HTML files, but if the file is
significantly ill-formed the parser may have problems.

I<denature> can add page breaks into the output pdf file. The placement of the page breaks
is controlled by comments inside the HTML file. When a page break is desired
place a <!-- PAGEBREAK --> command and a new page will be started before the next
block of text. This can have funny consequences if the pagebreak is place inside
a table data cell, so be careful where the break is placed.

=head1 OPTIONS

=over 5

=item B<-i dir> -- image directory.

Directory where images used in the HTML document are located.

=item B<-c dir> -- css file directory.

Directory where the CSS file used is located, if needed.

=item B<-l> -- landscape mode.

Will attempt to print the PDF as a landscape page (EXPERIMENTAL).

=item B<-s> -- SVG graphics.

This will enable the use of SVG graphics to draw some portions of the input 
fields. This requires a vaild DISPLAY variable to be set and a vaild X server
because the java.awt packages are used to draw the images.

=item B<-f> -- footer.

Will print a footer on the bottom of each page containing p. <page num>.

=item B<-e> -- header.

Will print a header on the top of each page containing the <title></title> contents.

=item B<-p> -- padding.

Will turn on the usage of the padding tag in CSS. This can cause some funny things
in the output at the moment.

=item B<-w> -- warning.

Will print out warning information.

=item B<-d> -- debug.

Will print out debug information (this includes warnings). Only needed for development.

=item B<-t> -- test only

Used to test denature, does not run fop and leaves the temporary xml file around.

=item B<-h> -- help.

Prints out a brief help message.

=back

=head1 ENVIRONMENT

There are several external dependancies which I<denature> requres to execute. They are:

=over 5

=item HTML-Tagset (perl module)

=item HTML-Parser (perl module)

=item HTML-Tree (perl module)

=item CSS::Tiny (perl module)

=item FOP

=item JDK

=back

The perl modules should be available through CPAN I<http://www.cpan.org>, while FOP is 
available from I<http://xml.apache.org/fop/index.html>.

Once all the requirements are installed two variables at the top of the I<denature> script 
need to be set to the FOP and JDK install directories. The variables are:

=over 5

=item $fop_home

The directory that contains the fop.sh script.

=item $java_home

The directory that contains the JDK (This is the same as the JAVA_HOME environment variable).
The the JDK version required should be specified on the FOP website.

=back

Once these variables are set I<denature> should work correctly. 

=head1 LIMITATIONS

I<denature> does not currently handle all of HTML. As unknown tags are encountered they are added in.

Not all <form> elements are handled, or only handled if the SVG flag is passed. The others could be
added, I just have not had time. Current text fields just have there values printed, they could have
boxes drawn around them in the future.

The CSS support in I<denature> only supports the simple items of TD.style or .style.

=head1 VERSION

This is I<denature> 0.6.5.

=head1 AUTHOR

dan sinclair <dan.sinclair@treklogic.com>

=cut



