File Coverage

blib/lib/Tk/HyperText.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Tk::HyperText;
2              
3             ##########################################################
4             # Look to the end of this file for the POD documentation #
5             ##########################################################
6              
7 1     1   21305 use strict;
  1         3  
  1         33  
8 1     1   5 use warnings;
  1         1  
  1         38  
9 1     1   4 use base qw(Tk::Derived Tk::ROText);
  1         8  
  1         880  
10             use Tk::PNG;
11             use Tk::JPEG;
12             use Tk::BrowseEntry;
13             use Tk::Listbox;
14             use Tk::Text;
15             use HTML::TokeParser;
16             use Data::Dumper;
17             use URI::Escape;
18              
19             our $VERSION = '0.10';
20              
21             Construct Tk::Widget 'HyperText';
22              
23             sub Populate {
24             my ($cw,$args) = @_;
25              
26             # Strip out the custom arguments for this widget.
27             my $opts = {
28             -attributes => {
29             -anchor => {
30             -normal => '#0000FF',
31             -hover => '#FF0000',
32             -active => '#FF0000',
33             -visited => '#990099',
34             },
35             -font => {
36             -family => 'Times',
37             -mono => 'Courier',
38             -size => 'medium',
39             -bold => 0, # Bold
40             -italic => 0, # Italic
41             -under => 0, # Underline
42             -over => 0, # Overstrike
43             },
44             -style => {
45             -margins => 0,
46             -color => '#000000', # Text color
47             -back => '#FFFFFF', # Text back
48             },
49             },
50             -continuous => 0,
51             -allow => [],
52             -deny => [],
53             };
54              
55             # Copy attributes over.
56             if (exists $args->{'-attributes'}) {
57             my $attr = delete $args->{'-attributes'};
58             foreach my $tag (keys %{$attr}) {
59             foreach my $name (keys %{$attr->{$tag}}) {
60             $opts->{'-attributes'}->{$tag}->{$name} =
61             $attr->{$tag}->{$name};
62             }
63             }
64             }
65              
66             # Copy other options over.
67             $opts->{'-continuous'} = delete $args->{'-continuous'} || delete $args->{'-continue'};
68             $opts->{'-allow'} = delete $args->{'-allow'} || [];
69             $opts->{'-deny'} = delete $args->{'-deny'} || [];
70              
71             # Pass the remaining arguments to ROText.
72             $args->{'-foreground'} = $opts->{'-attributes'}->{'-style'}->{'-color'};
73             $args->{'-background'} = $opts->{'-attributes'}->{'-style'}->{'-back'};
74             $cw->SUPER::Populate($args);
75              
76             # Reconfigure the ROText widget with our attributes.
77             $cw->SUPER::configure (
78             -highlightthickness => 0,
79             -exportselection => 1,
80             -insertofftime => 1000,
81             -insertontime => 0,
82             -cursor => undef,
83             -font => [
84             -family => $opts->{'-attributes'}->{'-font'}->{'-family'},
85             -size => $cw->_size ($opts->{'-attributes'}->{'-font'}->{'-size'}),
86             ],
87             );
88              
89             $cw->{hypertext} = {
90             html => '', # Holds the HTML code
91             continue => $opts->{'-continuous'},
92             attrib => $opts->{'-attributes'},
93             history => {},
94             events => {},
95             permissions => 'allow_all',
96             allow => {},
97             deny => {},
98             };
99              
100             if (scalar @{$opts->{'-allow'}}) {
101             $cw->allowedTags (@{$opts->{'-allow'}});
102             }
103             if (scalar @{$opts->{'-deny'}}) {
104             $cw->deniedTags (@{$opts->{'-deny'}});
105             }
106             }
107              
108             sub setHandler {
109             my ($cw,%handlers) = @_;
110              
111             foreach my $event (keys %handlers) {
112             my $code = $handlers{$event};
113             $cw->{hypertext}->{events}->{$event} = $code;
114             }
115             }
116              
117             sub _event {
118             my ($cw,$event,@args) = @_;
119              
120             if (exists $cw->{hypertext}->{events}->{$event}) {
121             return &{$cw->{hypertext}->{events}->{$event}} ($cw,@args);
122             }
123              
124             return undef;
125             }
126              
127             sub loadString {
128             my $cw = shift;
129             my $text = shift;
130              
131             # Clear the widget.
132             $cw->loadBlank();
133              
134             # Set the HTML buffer = our string.
135             $cw->{hypertext}->{html} = $text;
136             $cw->{hypertext}->{plain} = $text;
137             $cw->{hypertext}->{plain} =~ s/<(.|\n)+?>//sig;
138              
139             # Render the text.
140             $cw->render ($text);
141             }
142              
143             sub loadBlank {
144             my $cw = shift;
145             $cw->{hypertext}->{html} = '';
146             $cw->{hypertext}->{plain} = '';
147             $cw->delete ("0.0","end");
148             }
149              
150             sub allowedTags {
151             my ($cw,@tags) = @_;
152             $cw->{hypertext}->{allow} = {};
153             foreach (@tags) {
154             $_ = lc($_);
155             $cw->{hypertext}->{allow}->{$_} = 1;
156             }
157             }
158              
159             sub deniedTags {
160             my ($cw,@tags) = @_;
161             $cw->{hypertext}->{deny} = {};
162             foreach (@tags) {
163             $_ = lc($_);
164             $cw->{hypertext}->{deny}->{$_} = 1;
165             }
166             }
167              
168             sub allowHypertext {
169             my $cw = shift;
170              
171             # Allow AIM-style HTML tags.
172             my @allow = qw(html head title body a p br hr
173             img font center sup sub b i u s);
174             $cw->{hypertext}->{allow} = {};
175             $cw->{hypertext}->{deny} = {};
176              
177             foreach (@allow) {
178             $cw->{hypertext}->{allow}->{$_} = 1;
179             }
180             }
181              
182             sub allowEverything {
183             my $cw = shift;
184              
185             # Allow everything again.
186             $cw->{hypertext}->{allow} = {};
187             $cw->{hypertext}->{deny} = {};
188             }
189              
190             sub getText {
191             my $cw = shift;
192             my $asHTML = shift || 0;
193              
194             if ($asHTML) {
195             return $cw->{hypertext}->{html};
196             }
197             return $cw->{hypertext}->{plain};
198             }
199              
200             sub clearHistory {
201             my $cw = shift;
202             $cw->{hypertext}->{history} = {};
203             }
204              
205             sub render {
206             my ($cw,$html) = @_;
207              
208             # Initialize the style stack.
209             my $mAttr = $cw->{hypertext}->{attrib};
210             my %style = (
211             weight => 'normal', # or 'bold'
212             slant => 'roman', # or 'italic'
213             underline => 0, # or 1
214             overstrike => 0, # or 1
215             family => $mAttr->{'-font'}->{'-family'},
216             size => $mAttr->{'-font'}->{'-size'},
217             foreground => '',
218             background => '',
219             justify => 'left', # or 'center' or 'right'
220             offset => 0, # for and
221             lmargin1 => 0, # for
222             lmargin2 => 0, # and
223             rmargin => 0, # and
224             pre => 0, # inside
 tags 
225             linking => 0, # inside ... tags
226             linktag => '', # Current linktag
227             inul => 0, # Inside
228             inol => 0, # Inside
229             ullevel => 0,
230             ollevel => 0,
231             intable => 0,
232             intd => 0,
233             );
234             my @escape = (
235             '<' => '<',
236             '>' => '>',
237             '"' => '"',
238             ''' => "'",
239             ' ' => ' ',
240             '®' => chr(0x00ae),
241             '©' => chr(0x00a9),
242             '♥' => chr(0x2665),
243             '♦' => chr(0x2666),
244             '♠' => chr(0x2660),
245             '♣' => chr(0x2663),
246             '&' => '&',
247             );
248             my @stackList = ();
249             my $ulLevel = 0;
250             my $olLevel = 0;
251             my @stackOLLevel = ();
252             my @stackULLevel = ();
253             my $ulStyles = {};
254             my $olStyles = {};
255             my %hyperlink = (); # Hyperlink tags
256             my $tabledata = {}; # Table data
257             my $tableid = 0; # Table ID
258             my $formdata = {}; # Form data
259             my $formname = ''; # Current form name
260             my $curSelect = { # Selectbox data
261             in => 0, # Not in a
262             opts => [], # Options
263             name => '', # Name
264             size => 1, # Size
265             multiple => 0, # Multiple
266             state => 'readonly',
267             };
268             my (@stack) = $cw->_addStack (\%style);
269              
270             # Initialize the Text widget that gets our attention.
271             my $browser = $cw;
272              
273             # Initialize the parser.
274             my $parser = HTML::TokeParser->new (\$html);
275             $parser->xml_mode(1);
276             $parser->strict_names(1);
277             $parser->marked_sections(1);
278             my $foundOneBody = 0;
279             my $end = 0;
280             my $lineWritten = 0; # 1 = a line of text was written
281             while (my $token = $parser->get_token) {
282             my @data = @{$token};
283              
284             if ($data[0] eq "T") { # Plain Text
285             my $text = $data[1];
286             $text =~ s/([A-Za-z0-9]+)(\n+)([A-Za-z0-9]+)/$1 $3/ig;
287              
288             # Process escape sequences.
289             while ($text =~ /&#x([^;]+?)\;/i) {
290             my $hex = $1;
291             my $qm = quotemeta("&#x$hex");
292             my $chr = hex $hex;
293             my $char = chr($chr);
294             $text =~ s/$qm/$char/ig;
295             }
296             while ($text =~ /&#([^;]+?)\;/i) {
297             my $decimal = $1;
298             my $hex = sprintf("%x", $decimal);
299             my $qm = quotemeta("&#$decimal;");
300             my $chr = hex $hex;
301             my $char = chr($chr);
302             $text =~ s/$qm/$char/ig;
303             }
304             for (my $i = 0; $i < scalar(@escape) - 1; $i += 2) {
305             my $qm = quotemeta($escape[$i]);
306             my $rep = $escape[$i + 1];
307             $text =~ s/$qm/$rep/ig;
308             }
309              
310             # Unless in
, remove newlines. 
311             unless ($style{pre}) {
312             $text =~ s/[\x0d\x0a]//g;
313              
314             # If there's no text, skip this.
315             if ($text =~ /^[\s\t]+$/) {
316             next;
317             }
318             $text =~ s/^[\s\t]+/ /g;
319             $text =~ s/[\s\t]+$/ /g;
320             }
321              
322             # Generate a tag.
323             my $tag = '';
324             $tag = $cw->_makeTag(\%style,$browser);
325              
326             # Is this a hyperlink?
327             if ($style{linking}) {
328             # Bind this tag to an event.
329             my $href = $hyperlink{$style{linktag}}->{href};
330             my $target = $hyperlink{$style{linktag}}->{target};
331              
332             # Style up the initial color and underline.
333             if (exists $cw->{hypertext}->{history}->{$href}) {
334             $style{foreground} = $mAttr->{'-anchor'}->{'-visited'};
335             }
336             else {
337             $style{foreground} = $mAttr->{'-anchor'}->{'-normal'};
338             }
339             $style{underline} = 1;
340             push (@stack, $cw->_addStack(\%style));
341             $tag = $cw->_makeTag(\%style,$browser);
342              
343             my $codeClick = sub {
344             my ($parent,$tag,$href,$target) = @_;
345              
346             # Add this link to the history.
347             $parent->{hypertext}->{history}->{$href} = 1;
348              
349             # Recolor this link.
350             $parent->SUPER::tagConfigure ($tag,
351             -foreground => $mAttr->{'-anchor'}->{'-active'},
352             );
353              
354             # Call the link command.
355             $cw->_event ('Resource',
356             tag => 'a',
357             src => $href,
358             href => $href,
359             target => $target,
360             );
361             };
362             my $codeHover = sub {
363             my ($parent,$tag) = @_;
364             $parent->SUPER::configure (
365             -cursor => 'hand2',
366             );
367             $parent->SUPER::tagConfigure ($tag,
368             -foreground => $mAttr->{'-anchor'}->{'-active'},
369             );
370             };
371             my $codeOut = sub {
372             my ($parent,$tag,$href) = @_;
373             $parent->SUPER::configure (
374             -cursor => undef,
375             );
376              
377             if (exists $parent->{hypertext}->{history}->{$href}) {
378             $parent->SUPER::tagConfigure ($tag,
379             -foreground => $mAttr->{'-anchor'}->{'-visited'},
380             );
381             }
382             else {
383             $parent->SUPER::tagConfigure ($tag,
384             -foreground => $mAttr->{'-anchor'}->{'-normal'},
385             );
386             }
387             };
388              
389             # Bind the clicking of the link.
390             $browser->tagBind ($tag,"", [ $codeClick,
391             $tag, $href, $target ]);
392              
393             # Set up the hand cursor.
394             $browser->tagBind ($tag,"", [ $codeHover,
395             $tag ]);
396             $browser->tagBind ($tag,"", [ $codeOut,
397             $tag, $href ]);
398             }
399              
400             # Insert the plain text.
401             if (length $text > 0) {
402             $browser->insert ('end', $text, $tag);
403             $lineWritten = 1;
404             }
405              
406             if ($style{linking}) {
407             # Rollback the link styles.
408             %style = $cw->_rollbackStack(\@stack,
409             qw(foreground underline));
410             }
411             }
412             elsif ($data[0] eq "S") { # Start Tag
413             # Skip blocked tags.
414             next if $cw->_blockedTag ($data[1]);
415              
416             my $tag = lc($data[1]);
417             my $format = $cw->_makeTag(\%style);
418             if ($tag =~ /^(html|head)$/) { # HTML, HEAD
419             # That was nice of them.
420             }
421             elsif ($tag eq "title") { # Title
422             my $title = $parser->get_text("title", "/title");
423             $cw->_event ('Title',$title);
424             }
425             elsif ($tag eq "body") { # Body
426             my $at = $data[2];
427              
428             my ($bg,$fg,$link,$alink,$vlink);
429             if (exists $at->{bgcolor}) {
430             $bg = $at->{bgcolor} || "#FFFFFF";
431             }
432             if (exists $at->{text}) {
433             $fg = $at->{text} || "#000000";
434             }
435             if (exists $at->{link}) {
436             $link = $at->{link};
437             $mAttr->{'-anchor'}->{'-normal'} = $link || "#0000FF";
438             }
439             if (exists $at->{vlink}) {
440             $vlink = $at->{vlink};
441             $mAttr->{'-anchor'}->{'-visited'} = $vlink || "#990099";
442             }
443             if (exists $at->{alink}) {
444             $alink = $at->{alink};
445             $mAttr->{'-anchor'}->{'-active'} = $alink || "#FF0000";
446             }
447              
448             if ($foundOneBody == 0) {
449             # This is the first tag found;
450             # apply its colors globally.
451             $bg = $mAttr->{'-style'}->{'-back'}
452             unless length $bg;
453             $fg = $mAttr->{'-style'}->{'-color'}
454             unless length $fg;
455             $browser->configure (
456             -background => $bg,
457             -foreground => $fg,
458             );
459              
460             $mAttr->{'-style'}->{'-back'} = $bg;
461             $mAttr->{'-style'}->{'-color'} = $fg;
462             $foundOneBody = 1;
463             }
464             else {
465             # The bg/fg colors only apply from here
466             # on out.
467             $style{background} = $bg;
468             $style{foreground} = $fg;
469             push (@stack, $cw->_addStack(\%style));
470             }
471             }
472             elsif ($tag eq "a") { # Hyperlink
473             my $at = $data[2];
474             my $href = $at->{href} || '';
475             my $target = $at->{target} || '';
476              
477             # Create a unique link tag for Tk::Text.
478             my $linktag = join("-",$href,$target);
479             $linktag .= '_' while exists $hyperlink{$linktag};
480             $hyperlink{$linktag} = {
481             href => $href, target => $target,
482             };
483              
484             $style{linking} = 1;
485             $style{linktag} = $linktag;
486             }
487             elsif ($tag eq "br") { # Line break
488             $browser->SUPER::insert ('end', "\n", $format);
489             $lineWritten = 0;
490             }
491             elsif ($tag eq 'p') { # Paragraph
492             $browser->insert ('end', "\n\n", $format);
493             $lineWritten = 0;
494             }
495             elsif ($tag eq 'form') { # Form
496             my $at = $data[2];
497             my $name = defined $at->{name} ? $at->{name} : 'untitledform';
498             my $action = defined $at->{action} ? $at->{action} : '';
499             my $method = defined $at->{method} ? $at->{method} : '';
500             my $enc = defined $at->{enctype} ? $at->{enctype} : '';
501              
502             # Start collecting the form data.
503             $formdata->{$name}->{form} = {
504             name => $name, action => $action, method => $method, enctype => $enc,
505             };
506             $formname = $name;
507             }
508             elsif ($tag eq 'textarea') { # Textarea
509             my $at = $data[2];
510             my $name = defined $at->{name} ? $at->{name} : 'x_not_a_form_field';
511             my $cols = defined $at->{cols} ? $at->{cols} : 20;
512             my $rows = defined $at->{rows} ? $at->{rows} : 4;
513             my $state = defined $at->{disabled} ? 'disabled' : 'normal';
514             my $wrap = 'word';
515             if (defined $at->{wrap}) {
516             if ($at->{wrap} eq 'off') {
517             $wrap = 'none';
518             }
519             }
520              
521             my $value = $parser->get_text("textarea", "/textarea");
522              
523             $formdata->{$formname}->{fields}->{$name} = $value;
524             $formdata->{$formname}->{defaults}->{$name} = $value;
525              
526             my $widget = $browser->Text (
527             #-scrollbars => 'ose',
528             -wrap => $wrap,
529             -width => $cols,
530             -height => $rows,
531             -font => [
532             -family => 'Courier',
533             -size => 12,
534             ],
535             -foreground => '#000000',
536             -background => '#FFFFFF',
537             -highlightthickness => 0,
538             -border => 1,
539             );
540             $widget->insert('end',$value);
541             $browser->windowCreate('end',
542             -window => $widget,
543             -align => 'baseline',
544             );
545             }
546             elsif ($tag eq 'select') { # Selectbox
547             my $at = $data[2];
548             my $name = defined $at->{name} ? $at->{name} : 'x_not_a_form_field';
549             my $size = defined $at->{size} ? $at->{size} : 1;
550             my $mult = defined $at->{multiple} ? 1 : 0;
551             my $state = defined $at->{disabled} ? 'disabled' : 'readonly';
552             $curSelect->{in} = 1;
553             $curSelect->{opts} = [];
554             $curSelect->{name} = $name;
555             $curSelect->{size} = $size;
556             $curSelect->{multiple} = $mult;
557             $curSelect->{state} = $state;
558             }
559             elsif ($tag eq 'option') { # Option
560             my $at = $data[2];
561             my $name = $curSelect->{name};
562             my $value = defined $at->{value} ? $at->{value} : '';
563             my $label = $parser->get_text("option","/option");
564              
565             # Selected?
566             if (exists $at->{selected} || !exists $formdata->{$formname}->{fields}->{$name}) {
567             $formdata->{$formname}->{fields}->{$name} = $label;
568             $formdata->{$formname}->{defaults}->{$name} = $value;
569             }
570              
571             if ($curSelect->{in}) {
572             push (@{$curSelect->{opts}}, [ $value, $label ]);
573             }
574             }
575             elsif ($tag eq 'input') { # Input
576             my $at = $data[2];
577             my $name = defined $at->{name} ? $at->{name} : 'x_not_a_form_field';
578              
579             my $type = defined $at->{type} ? $at->{type} : 'text';
580             my $size = defined $at->{size} ? $at->{size} : 15;
581             my $value = defined $at->{value} ? $at->{value} : '';
582             my $max = defined $at->{maxlength} ? $at->{maxlength} : 0;
583             my $state = defined $at->{disabled} ? 'disabled' : 'normal';
584             my $checked = defined $at->{checked} ? 'checked' : 'cleared';
585              
586             $type = lc($type);
587             $type = 'text' unless $type =~ /^(text|password|button|checkbox|radio|submit|reset)$/i;
588              
589             # Initialize the form variable.
590             $formdata->{$formname}->{fields}->{$name} = $value unless exists $formdata->{$formname}->{fields}->{$name};
591             $formdata->{$formname}->{defaults}->{$name} = $value unless exists $formdata->{$formname}->{defaults}->{$name};
592              
593             # Insert the widgets.
594             if ($type eq 'text') {
595             my $widget = $browser->Entry (
596             -textvariable => \$formdata->{$formname}->{fields}->{$name},
597             -width => $size,
598             -state => $state,
599             -background => '#FFFFFF',
600             -foreground => '#000000',
601             -font => [
602             -family => 'Helvetica',
603             -size => 10,
604             ],
605             -highlightthickness => 0,
606             -border => 1,
607             );
608             $browser->windowCreate ('end',
609             -window => $widget,
610             -align => 'baseline',
611             );
612             }
613             if ($type eq 'password') {
614             my $widget = $browser->Entry (
615             -textvariable => \$formdata->{$formname}->{fields}->{$name},
616             -show => '*',
617             -state => $state,
618             -width => $size,
619             -background => '#FFFFFF',
620             -foreground => '#000000',
621             -font => [
622             -family => 'Helvetica',
623             -size => 10,
624             ],
625             -highlightthickness => 0,
626             -border => 1,
627             );
628             $browser->windowCreate ('end',
629             -window => $widget,
630             -align => 'baseline',
631             );
632             }
633             elsif ($type eq 'checkbox') {
634             if ($checked eq 'cleared') {
635             $formdata->{$formname}->{fields}->{$name} = '';
636             }
637              
638             my $widget = $browser->Checkbutton (
639             -variable => \$formdata->{$formname}->{fields}->{$name},
640             -state => $state,
641             -onvalue => $formdata->{$formname}->{defaults}->{$name},
642             -offvalue => '',
643             -text => '',
644             -background => $style{background} || $mAttr->{'-style'}->{'-back'},
645             -activebackground => $style{background} || $mAttr->{'-style'}->{'-back'},
646             -highlightthickness => 0,
647             );
648             $browser->windowCreate ('end',
649             -window => $widget,
650             -align => 'baseline',
651             );
652             }
653             elsif ($type eq 'radio') {
654             if ($checked eq 'checked') {
655             $formdata->{$formname}->{fields}->{$name} = $value;
656             }
657              
658             my $widget = $browser->Radiobutton (
659             -variable => \$formdata->{$formname}->{fields}->{$name},
660             -state => $state,
661             -value => $value,
662             -text => '',
663             -background => $style{background} || $mAttr->{'-style'}->{'-back'},
664             -activebackground => $style{background} || $mAttr->{'-style'}->{'-back'},
665             -highlightthickness => 0,
666             );
667             $browser->windowCreate ('end',
668             -window => $widget,
669             -align => 'baseline',
670             );
671             }
672             elsif ($type =~ /^(button|submit|reset)$/i) {
673             my $widget = $browser->Button (
674             -text => $value,
675             -state => $state,
676             -cursor => '',
677             -highlightthickness => 0,
678             -border => 1,
679             -font => [
680             -family => 'Helvetica',
681             -size => 10,
682             ],
683             );
684             $browser->windowCreate ('end',
685             -window => $widget,
686             -align => 'baseline',
687             );
688              
689             # Submit buttons submit the form.
690             if ($type eq 'submit') {
691             $widget->configure (-command => sub {
692             # Collect all the fields.
693             my $fields = ();
694             foreach my $f (keys %{$formdata->{$formname}->{fields}}) {
695             next if $f eq 'x_not_a_form_field';
696             $fields->{$f} = $formdata->{$formname}->{fields}->{$f};
697             }
698              
699             # If there are any listboxes, get them too.
700             if (exists $formdata->{$formname}->{listwidget}) {
701             foreach my $w (keys %{$formdata->{$formname}->{listwidget}}) {
702             my @in = $formdata->{$formname}->{listwidget}->{$w}->curselection();
703             if (scalar(@in) > 1) {
704             my $values = [];
705             foreach my $i (@in) {
706             my $v = $formdata->{$formname}->{listwidget}->{$w}->get ($i);
707             push (@{$values}, $v);
708             }
709             $fields->{$w} = $values;
710             }
711             elsif (scalar(@in) == 1) {
712             $fields->{$w} = $formdata->{$formname}->{listwidget}->{$w}->get ($in[0]);
713             }
714             else {
715             $fields->{$w} = undef;
716             }
717             }
718             }
719              
720             # Submit the form.
721             $cw->_event ('Submit',
722             form => $formdata->{$formname}->{form}->{name},
723             action => $formdata->{$formname}->{form}->{action},
724             method => $formdata->{$formname}->{form}->{method},
725             enctype => $formdata->{$formname}->{form}->{enctype},
726             fields => $fields,
727             );
728             });
729             }
730              
731             # Reset buttons reset the form.
732             if ($type eq 'reset') {
733             $widget->configure (-command => sub {
734             # Reset all the fields.
735             foreach my $f (keys %{$formdata->{$formname}->{defaults}}) {
736             $formdata->{$formname}->{fields}->{$f} = $formdata->{$formname}->{defaults}->{$f};
737             }
738             });
739             }
740             }
741             }
742             elsif ($tag eq 'table') { # Table
743             $browser->insert ('end', "\n") if $lineWritten;
744             my $at = $data[2];
745             my $border = $at->{border} || 0;
746             my $cellspacing = $at->{cellspacing} || 0;
747             my $cellpadding = $at->{cellpadding} || 0;
748             $tableid++;
749             $tabledata->{$tableid}->{widget} =
750             $cw->Frame (
751             -takefocus => 0,
752             -highlightthickness => 0,
753             -relief => 'raised',
754             -borderwidth => $cw->_isNumber ($border,0),
755             -background => $style{background} || $mAttr->{'-style'}->{'-back'},
756             );
757             $tabledata->{$tableid}->{row} = -1;
758             $tabledata->{$tableid}->{col} = -1;
759             $tabledata->{$tableid}->{border} = $cw->_isNumber ($border,0);
760             $tabledata->{$tableid}->{cellspacing} = $cw->_isNumber ($cellspacing,0);
761             $tabledata->{$tableid}->{cellpadding} = $cw->_isNumber ($cellpadding,0);
762             $browser->windowCreate ('end',
763             -window => $tabledata->{$tableid}->{widget},
764             -align => 'baseline',
765             );
766             $style{intable} = 1;
767             push (@stack, $cw->_addStack(\%style));
768             }
769             elsif ($tag eq "tr") { # Table Row
770             if ($style{intable}) {
771             $tabledata->{$tableid}->{col} = -1;
772             $tabledata->{$tableid}->{row}++;
773             }
774             }
775             elsif ($tag =~ /^(td|th|thead|tbody|tfoot)$/) { # Table Data
776             if ($style{intable}) {
777             my $at = $data[2];
778             my $colspan = undef;
779             my $rowspan = undef;
780             if (defined $at->{colspan}) {
781             $colspan = $at->{colspan};
782             } if (defined $at->{rowspan}) {
783             $rowspan = $at->{rowspan};
784             }
785             $style{intd} = 1;
786             $tabledata->{$tableid}->{col}++;
787             $browser = $tabledata->{$tableid}->{widget}->ROText (
788             -exportselection => 1,
789             -takefocus => 0,
790             -highlightthickness => 0,
791             -relief => 'sunken',
792             -wrap => 'word',
793             -borderwidth => $tabledata->{$tableid}->{border},
794             -insertofftime => 1000,
795             -insertontime => 0,
796             -width => 0,
797             -height => 2,
798             -padx => $tabledata->{$tableid}->{cellpadding},
799             -pady => $tabledata->{$tableid}->{cellpadding},
800             -foreground => $style{foreground} || $mAttr->{'-style'}->{'-color'},
801             -background => $style{background} || $mAttr->{'-style'}->{'-back'},
802             -cursor => undef,
803             -font => [
804             -family => $style{family},
805             -weight => $style{weight},
806             -slant => $style{slant},
807             -size => $cw->_size ($style{size}),
808             -underline => $style{underline},
809             -overstrike => $style{overstrike},
810             ],
811             );
812             my @spans = ();
813             push (@spans, '-columnspan' => $colspan) if defined $colspan;
814             push (@spans, '-rowspan' => $rowspan) if defined $rowspan;
815             $browser->grid (
816             -row => $tabledata->{$tableid}->{row},
817             -column => $tabledata->{$tableid}->{col},
818             -sticky => 'nsew',
819             -padx => $tabledata->{$tableid}->{cellspacing},
820             -pady => $tabledata->{$tableid}->{cellspacing},
821             @spans,
822             );
823             $lineWritten = 0;
824             }
825             push (@stack, $cw->_addStack(\%style));
826             }
827             elsif ($tag eq 'hr') { # HR
828             my $at = $data[2];
829             my $height = 4;
830             if (exists $at->{size}) {
831             $height = $at->{size};
832             }
833             my $width = $cw->screenwidth;
834             my $frame = $browser->Frame (
835             -relief => 'raised',
836             -height => $height,
837             -width => $width,
838             -borderwidth => 1,
839             -highlightthickness => 0,
840             );
841             $browser->insert ('end', "\n", $format);
842             $browser->windowCreate ('end',
843             -window => $frame,
844             -padx => 0,
845             -pady => 5,
846             );
847             $browser->insert ('end', "\n", $format);
848             $lineWritten = 0;
849             }
850             elsif ($tag eq 'img') { # IMG
851             my $at = $data[2];
852              
853             my $format = '';
854             my $align = lc($at->{align}) || '';
855             $align = 'baseline' unless $align =~ /^(top|center|bottom|baseline)$/;
856             if (length $at->{src}) {
857             my ($ext) = $at->{src} =~ /\.([^\.]+)$/i;
858             if ($ext =~ /^gif$/i) {
859             $format = 'GIF';
860             }
861             elsif ($ext =~ /^png$/i) {
862             $format = 'PNG';
863             }
864             elsif ($ext =~ /^(jpeg|jpe|jpg)$/i) {
865             $format = 'JPEG';
866             }
867             elsif ($ext =~ /^bmp$/i) {
868             $format = 'BMP';
869             }
870             }
871              
872             my $broken = 0;
873              
874             # Request this resource.
875             my $data = $cw->_event ('Resource',
876             tag => 'img',
877             src => $at->{src} || '',
878             width => $at->{width} || '',
879             height => $at->{height} || '',
880             vspace => $at->{vspace} || '',
881             hspace => $at->{hspace} || '',
882             align => $at->{align} || '',
883             alt => $at->{alt} || '',
884             );
885             $data = '' unless defined $data;
886              
887             # Invalid format?
888             if (length $format == 0 || length $data == 0) {
889             $broken = 1;
890             }
891              
892             if (length $data > 0 && not $broken) {
893             my $image = $cw->Photo (
894             -data => $data,
895             -format => $format,
896             );
897             $browser->imageCreate ('end',
898             -image => $image,
899             -align => $align,
900             -padx => $cw->_isNumber($at->{hspace},2),
901             -pady => $cw->_isNumber($at->{vspace},2),
902             );
903             }
904             else {
905             my $image = $cw->Photo (
906             -data => $cw->_brokenImage(),
907             -format => 'PNG',
908             );
909             $browser->imageCreate ('end',
910             -image => $image,
911             -align => $align,
912             -padx => $cw->_isNumber($at->{hspace},2),
913             -pady => $cw->_isNumber($at->{vspace},2),
914             );
915             }
916              
917             $lineWritten = 1;
918             }
919             elsif ($tag eq 'font' || $tag eq 'basefont') { # Font
920             my $at = $data[2];
921              
922             if (exists $at->{face}) {
923             $style{family} = $at->{face};
924             }
925             if (exists $at->{size}) {
926             $style{size} = $at->{size};
927             }
928             if (exists $at->{color}) {
929             $style{foreground} = $at->{color};
930             }
931             if (exists $at->{back}) {
932             $style{background} = $at->{back};
933             }
934              
935             push (@stack, $cw->_addStack(\%style));
936             }
937             elsif ($tag =~ /^h(1|2|3|4|5|6)$/) { # Heading
938             my $level = $1;
939             my $size = $cw->_heading($level);
940             $browser->insert ('end',"\n\n") if $lineWritten;
941             $style{size} = $size;
942             $style{weight} = 'bold';
943             push (@stack, $cw->_addStack(\%style));
944             }
945             elsif ($tag eq "ol") { # Ordered List
946             my $at = $data[2];
947             if ($style{inol} == 0 && $style{inul} == 0 && $lineWritten) {
948             $browser->insert ('end',"\n\n");
949             }
950             elsif ($style{inol} || $style{inul}) {
951             $browser->insert ('end',"\n");
952             }
953             $style{lmargin1} += 15;
954             $style{lmargin2} += 30;
955             $style{inol}++;
956             $olLevel++;
957              
958             my $type = 1;
959             my $start = 1;
960             if (defined $at->{type}) {
961             $type = $at->{type};
962             }
963             if (defined $at->{start}) {
964             $start = $at->{start};
965             }
966              
967             $olStyles->{$olLevel} = {
968             type => $type,
969             position => $start,
970             };
971              
972             push (@stackList,join('#','ol',$olLevel));
973             push (@stackOLLevel,$olLevel);
974              
975             push (@stack, $cw->_addStack(\%style));
976             }
977             elsif ($tag eq "ul") { # Unordered List
978             my $at = $data[2];
979             if ($style{inol} == 0 && $style{inul} == 0 && $lineWritten) {
980             $browser->insert ('end',"\n\n");
981             }
982             elsif ($style{inol} || $style{inul}) {
983             $browser->insert ('end',"\n");
984             }
985             $style{lmargin1} += 15;
986             $style{lmargin2} += 30;
987             $style{inul}++;
988             $ulLevel++;
989              
990             # Find out any style info.
991             my $type = "disc";
992             if (defined $at->{type}) {
993             $type = $at->{type};
994             }
995              
996             $ulStyles->{$ulLevel} = {
997             type => $type,
998             };
999              
1000             push (@stackList,join('#','ul',$ulLevel));
1001             push (@stackULLevel,$ulLevel);
1002              
1003             push (@stack, $cw->_addStack(\%style));
1004             }
1005             elsif ($tag eq 'li') { # List Item
1006             if (scalar(@stackList)) {
1007             my ($family,$level) = split(/#/, $stackList[-1], 2);
1008             my $kind = '';
1009             my $begin = 0;
1010             if ($family eq "ol") {
1011             $kind = $olStyles->{$level}->{type};
1012             $begin = $olStyles->{$level}->{position};
1013             }
1014             else {
1015             $kind = $ulStyles->{$level}->{type};
1016             $begin = 0;
1017             }
1018              
1019             if ($family eq "ol") {
1020             $olStyles->{$level}->{position}++;
1021             my $symbol = $cw->_getOLsym ($kind,$begin);
1022             $symbol .= ".";
1023             $symbol .= " " until length $symbol >= 8;
1024             $browser->insert ('end',"$symbol",$format);
1025             }
1026             else {
1027             my $symbol = $cw->_getULsym ($kind);
1028             $browser->insert ('end',"$symbol ",$format);
1029             }
1030             }
1031             }
1032             elsif ($tag eq 'blockquote') { # Blockquote
1033             $browser->insert ('end',"\n",$format) if $lineWritten;
1034             $style{lmargin1} += 25;
1035             $style{lmargin2} += 25;
1036             $style{rmargin} += 25;
1037             push (@stack, $cw->_addStack(\%style));
1038             }
1039             elsif ($tag eq 'div') { # Div
1040             my $at = $data[2];
1041             $browser->insert ('end',"\n",$format) if $lineWritten;
1042              
1043             if (exists $at->{align}) {
1044             if ($at->{align} =~ /^(center|left|right)$/i) {
1045             $style{justify} = lc($1);
1046             }
1047             }
1048              
1049             push (@stack, $cw->_addStack(\%style));
1050             }
1051             elsif ($tag eq 'span') { # Span
1052             push (@stack, $cw->_addStack(\%style));
1053             }
1054             elsif ($tag eq 'pre') { # Pre
1055             $browser->insert('end', "\n", $format) if $lineWritten;
1056             $style{family} = $mAttr->{'-font'}->{'-mono'};
1057             $style{pre} = 1;
1058             push (@stack, $cw->_addStack(\%style));
1059             }
1060             elsif ($tag =~ /^(code|tt|kbd|samp)$/) { # Code
1061             $style{family} = $mAttr->{'-font'}->{'-mono'};
1062             push (@stack, $cw->_addStack(\%style));
1063             }
1064             elsif ($tag =~ /^(center|right|left)$/) { # Alignment
1065             my $align = $1;
1066             $browser->insert ('end',"\n",$format);
1067             $style{justify} = lc($align);
1068             push (@stack, $cw->_addStack(\%style));
1069             }
1070             elsif ($tag eq 'sup') { # Superscript
1071             $style{size}--;
1072             $style{size} = 0 if $style{size} < 0;
1073             $style{offset} += 4;
1074             push (@stack, $cw->_addStack(\%style));
1075             }
1076             elsif ($tag eq 'sub') { # Subscript
1077             $style{size}--;
1078             $style{size} = 0 if $style{size} < 0;
1079             $style{offset} -= 2;
1080             push (@stack, $cw->_addStack(\%style));
1081             }
1082             elsif ($tag eq 'big') { # Big
1083             $style{size}++;
1084             push (@stack, $cw->_addStack(\%style));
1085             }
1086             elsif ($tag eq 'small') { # Small
1087             $style{size}--;
1088             push (@stack, $cw->_addStack(\%style));
1089             }
1090             elsif ($tag =~ /^(b|strong)$/) { # Bold
1091             $style{weight} = "bold";
1092             push (@stack, $cw->_addStack(\%style));
1093             }
1094             elsif ($tag =~ /^(i|em|address|var|cite|def)$/) { # Italic
1095             $style{slant} = "italic";
1096             push (@stack, $cw->_addStack(\%style));
1097             }
1098             elsif ($tag =~ /^(u|ins)$/) { # Underline
1099             $style{underline} = 1;
1100             push (@stack, $cw->_addStack(\%style));
1101             }
1102             elsif ($tag =~ /^(s|del)$/) { # Strike-out
1103             $style{overstrike} = 1;
1104             push (@stack, $cw->_addStack(\%style));
1105             }
1106             }
1107             elsif ($data[0] eq "E") { # End Tag
1108             # Skip blocked tags.
1109             next if $cw->_blockedTag ($data[1]);
1110              
1111             my $tag = lc($data[1]);
1112             my $format = $cw->_makeTag(\%style);
1113             if ($tag =~ /^(html|head)$/) { # /HTML, /HEAD
1114             # That was nice of them.
1115             }
1116             elsif ($tag eq 'title') { # /Title
1117             # Ignore; we already got the title.
1118             }
1119             elsif ($tag eq 'body') { # /Body
1120             $browser->insert('end',"\n",$format);
1121             %style = $cw->_rollbackStack(\@stack,
1122             qw(foreground background));
1123             }
1124             elsif ($tag eq 'a') { # /A
1125             # We're not linking anymore.
1126             $style{linking} = 0;
1127             $style{linktag} = '';
1128             }
1129             elsif ($tag eq 'p') { # /Paragraph
1130             $browser->insert('end',"\n\n",$format);
1131             $lineWritten = 0;
1132             }
1133             elsif ($tag eq 'table') { # /Table
1134             $browser->insert('end',"\n",$format);
1135             %style = $cw->_rollbackStack(\@stack,
1136             qw(intable));
1137             }
1138             elsif ($tag eq "tr") { # /Table Row
1139             # Do nothing.
1140             }
1141             elsif ($tag =~ /^(td|th|thead|tbody|tfoot)$/) { # /Table Data
1142             if ($style{intd}) {
1143             $style{intd} = 0;
1144             my $endline = $browser->index('end');
1145             $endline =~ s/\..*$//;
1146             my $i = 0;
1147             my $max = 0;
1148             while ($i++ < $endline) {
1149             my $l = length (
1150             $browser->get("$i.0","$i.0 lineend")
1151             );
1152             $max = $l if $l > $max;
1153             }
1154             $browser->configure (-width => $max,
1155             -height => $endline - 1);
1156             %style = $cw->_rollbackStack(\@stack,
1157             qw(intd));
1158              
1159             # Reset the browser.
1160             $browser = $cw;
1161             }
1162             }
1163             elsif ($tag eq 'select') { # /Select
1164             if ($curSelect->{in}) {
1165             # Collect the choices.
1166             my @choices = ();
1167             foreach my $choice (@{$curSelect->{opts}}) {
1168             push (@choices,$choice->[1] || $choice->[0]);
1169             }
1170              
1171             # Determine if we need a Listbox or a BrowseEntry.
1172             my $name = $curSelect->{name} || 'x_not_a_form_field';
1173             my $size = $curSelect->{size};
1174             my $mult = $curSelect->{multiple};
1175             $size = 1 unless $cw->_isNumber($size);
1176             if ($size <= 1) {
1177             # BrowseEntry.
1178             my $widget = $browser->BrowseEntry (
1179             -variable => \$formdata->{$formname}->{fields}->{$name},
1180             -choices => [ @choices ],
1181             -state => $curSelect->{state},
1182             -foreground => '#000000',
1183             -background => '#FFFFFF',
1184             -disabledforeground => '#000000',
1185             -disabledbackground => '#FFFFFF',
1186             -border => 1,
1187             -highlightthickness => 0,
1188             -font => [
1189             -family => 'Helvetica',
1190             -size => 10,
1191             ],
1192             );
1193             $browser->windowCreate ('end',
1194             -window => $widget,
1195             -align => 'baseline',
1196             );
1197             }
1198             else {
1199             # Listbox.
1200             $formdata->{$formname}->{listboxes}->{$name} = 1;
1201             $formdata->{$formname}->{listwidget}->{$name} = $browser->Listbox (
1202             -height => $size,
1203             -foreground => '#000000',
1204             -background => '#FFFFFF',
1205             -font => [
1206             -family => 'Helvetica',
1207             -size => 10,
1208             ],
1209             -selectmode => ($mult ? 'multiple' : 'single'),
1210             -exportselection => 0,
1211             -border => 1,
1212             -highlightthickness => 0,
1213             );
1214             $formdata->{$formname}->{listwidget}->{$name}->insert('end',@choices);
1215             $browser->windowCreate ('end',
1216             -window => $formdata->{$formname}->{listwidget}->{$name},
1217             -align => 'baseline',
1218             );
1219             }
1220             }
1221             }
1222             elsif ($tag eq 'font') { # /Font
1223             %style = $cw->_rollbackStack(\@stack,
1224             qw(family size color back));
1225             }
1226             elsif ($tag =~ /^h(1|2|3|4|5|6)$/) { # /Heading
1227             $browser->insert('end',"\n\n",$format);
1228             %style = $cw->_rollbackStack(\@stack,
1229             qw(size weight));
1230             $lineWritten = 0;
1231             }
1232             elsif ($tag eq 'ol') { # /Ordered List
1233             pop (@stackList);
1234             %style = $cw->_rollbackStack(\@stack,
1235             qw(lmargin1 lmargin2));
1236              
1237             my $lastLevel = pop(@stackOLLevel);
1238             $style{olLevel} = $stackOLLevel[-1] || 0;
1239             delete $olStyles->{$lastLevel};
1240              
1241             $style{inol}--;
1242             $olLevel--;
1243             $olLevel = 0 if $olLevel < 0;
1244             $style{inol} = 0 if $style{inol} < 0;
1245              
1246             if ($style{inol} || $style{inul}) {
1247             $browser->insert ('end',"\n",$format);
1248             $lineWritten = 0;
1249             }
1250             else {
1251             $browser->insert ('end',"\n\n",$format);
1252             $lineWritten = 0;
1253             }
1254             }
1255             elsif ($tag eq 'ul') { # /Unordered List
1256             pop (@stackList);
1257             %style = $cw->_rollbackStack(\@stack,
1258             qw(lmargin1 lmargin2));
1259              
1260             my $lastLevel = pop(@stackULLevel);
1261             $style{ulLevel} = $stackULLevel[-1] || 0;
1262             delete $ulStyles->{$lastLevel};
1263              
1264             $style{inul}--;
1265             $ulLevel--;
1266             $ulLevel = 0 if $ulLevel < 0;
1267             $style{inul} = 0 if $style{inul} < 0;
1268              
1269             if ($style{inol} || $style{inul}) {
1270             $browser->insert ('end',"\n",$format);
1271             $lineWritten = 0;
1272             }
1273             else {
1274             $browser->insert ('end',"\n\n",$format);
1275             $lineWritten = 0;
1276             }
1277             }
1278             elsif ($tag eq 'li') { # /LI
1279             $browser->insert('end',"\n",$format);
1280             $lineWritten = 0;
1281             }
1282             elsif ($tag eq 'blockquote') { # /Blockquote
1283             $browser->insert('end',"\n",$format);
1284             %style = $cw->_rollbackStack(\@stack,
1285             qw(lmargin1 lmargin2 rmargin));
1286             $lineWritten = 0;
1287             }
1288             elsif ($tag eq 'div') { # /Div
1289             $browser->insert('end',"\n",$format);
1290             %style = $cw->_rollbackStack(\@stack,'justify');
1291             $lineWritten = 0;
1292             }
1293             elsif ($tag eq 'span') { # /Span
1294             %style = $cw->_rollbackStack(\@stack);
1295             }
1296             elsif ($tag eq 'pre') { # /Pre
1297             $browser->insert ('end',"\n",$format);
1298             %style = $cw->_rollbackStack(\@stack,
1299             qw(family pre));
1300             }
1301             elsif ($tag =~ /^(code|tt|kbd|samp)$/) { # /Code
1302             %style = $cw->_rollbackStack(\@stack,'family');
1303             }
1304             elsif ($tag =~ /^(center|right|left)$/) { # /Align
1305             $browser->insert('end',"\n",$format);
1306             %style = $cw->_rollbackStack(\@stack,'justify');
1307             $lineWritten = 0;
1308             }
1309             elsif ($tag =~ /^(sup|sub)$/) { # /Superscript, /Subscript
1310             %style = $cw->_rollbackStack(\@stack,
1311             qw(size offset));
1312             }
1313             elsif ($tag =~ /^(big|small)$/) { # /Big, /Small
1314             %style = $cw->_rollbackStack(\@stack,'size');
1315             }
1316             elsif ($tag =~ /^(b|strong)$/) { # /Bold
1317             %style = $cw->_rollbackStack(\@stack,'weight');
1318             }
1319             elsif ($tag =~ /^(i|em|address|var|cite|def)$/) { # /Italic
1320             %style = $cw->_rollbackStack(\@stack,'slant');
1321             }
1322             elsif ($tag =~ /^(u|ins)$/) { # /Underline
1323             %style = $cw->_rollbackStack(\@stack,'underline');
1324             }
1325             elsif ($tag =~ /^(s|del)$/) { # /Overstrike
1326             %style = $cw->_rollbackStack(\@stack,'overstrike');
1327             }
1328             }
1329             }
1330             }
1331              
1332             sub _addStack {
1333             my ($cw,$style) = @_;
1334              
1335             my @keys = sort { $a cmp $b } keys %{$style};
1336             my @parts = ();
1337             foreach my $k (@keys) {
1338             my $val = $style->{$k};
1339             $val = uri_escape($val);
1340             push (@parts,join("=",$k,$val));
1341             }
1342              
1343             return join ("&",@parts);
1344             }
1345              
1346             sub _rollbackStack {
1347             my ($cw,$stack,@keys) = @_;
1348              
1349             my $newStyle = {};
1350             if (scalar @{$stack} > 1) {
1351             my $curStack = $stack->[-1];
1352             my $lastStack = $stack->[-2];
1353             my $curStyle = {};
1354             my $lastStyle = {};
1355              
1356             # Collect the style data.
1357             foreach my $p (split(/\&/, $curStack)) {
1358             my ($k,$val) = split(/=/, $p, 2);
1359             $val = uri_unescape($val);
1360             $curStyle->{$k} = $val;
1361             }
1362             foreach my $p (split(/\&/, $lastStack)) {
1363             my ($k,$val) = split(/=/, $p, 2);
1364             $val = uri_unescape($val);
1365             $lastStyle->{$k} = $val;
1366             }
1367              
1368             $newStyle = $lastStyle;
1369              
1370             # For @keys, set these values to what they were before.
1371             foreach my $k (@keys) {
1372             $newStyle->{$k} = (defined $lastStyle->{$k} &&
1373             length $lastStyle->{$k}) ? $lastStyle->{$k} : '';
1374             }
1375              
1376             pop(@{$stack});
1377             return %{$newStyle};
1378             }
1379             else {
1380             my $curStyle = {};
1381              
1382             foreach my $p (split(/\&/, $stack->[-1])) {
1383             my ($k,$val) = split(/=/, $p, 2);
1384             $val = uri_unescape($val);
1385             $curStyle->{$k} = $val;
1386             }
1387              
1388             return %{$curStyle};
1389             }
1390             }
1391              
1392             sub _makeTag {
1393             my ($cw,$style,$widget) = @_;
1394              
1395             my @parts = ();
1396             foreach my $k (sort { $a cmp $b } keys %{$style}) {
1397             my $val = uri_escape($style->{$k}) || '';
1398             push (@parts,$val);
1399             }
1400              
1401             my $tag = join("-",@parts);
1402              
1403             if (defined $widget) {
1404             $widget->tagConfigure ($tag,
1405             -foreground => $style->{foreground},
1406             -background => $style->{background},
1407             -font => [
1408             -family => $style->{family},
1409             -weight => $style->{weight},
1410             -slant => $style->{slant},
1411             -size => $cw->_size ($style->{size}),
1412             -underline => $style->{underline},
1413             -overstrike => $style->{overstrike},
1414             ],
1415             -offset => $style->{offset},
1416             -justify => $style->{justify},
1417             -lmargin1 => $style->{lmargin1},
1418             -lmargin2 => $style->{lmargin2},
1419             -rmargin => $style->{rmargin},
1420             );
1421             }
1422             else {
1423             $cw->SUPER::tagConfigure ($tag,
1424             -foreground => $style->{foreground},
1425             -background => $style->{background},
1426             -font => [
1427             -family => $style->{family},
1428             -weight => $style->{weight},
1429             -slant => $style->{slant},
1430             -size => $cw->_size ($style->{size}),
1431             -underline => $style->{underline},
1432             -overstrike => $style->{overstrike},
1433             ],
1434             -offset => $style->{offset},
1435             -justify => $style->{justify},
1436             -lmargin1 => $style->{lmargin1},
1437             -lmargin2 => $style->{lmargin2},
1438             -rmargin => $style->{rmargin},
1439             );
1440             }
1441              
1442             return $tag;
1443             }
1444              
1445             # Calculates the point size from an HTML size.
1446             sub _size {
1447             my ($cw,$size) = @_;
1448              
1449             # Translate words to numbers?
1450             if ($size =~ /[^0-9]/) {
1451             $size = $cw->_sizeStringToNumber ($size);
1452             }
1453              
1454             my %map = (
1455             # HTML => Point
1456             0 => 8,
1457             1 => 9,
1458             2 => 10,
1459             3 => 12,
1460             4 => 14,
1461             5 => 16,
1462             6 => 18,
1463             );
1464              
1465             return exists $map{$size} ? $map{$size} : 10;
1466             }
1467              
1468             # Calculates the HTML size for a heading.
1469             sub _heading {
1470             my ($cw,$level) = @_;
1471              
1472             my %map = (
1473             # Level => HTML Size
1474             1 => 6,
1475             2 => 5,
1476             3 => 4,
1477             4 => 3,
1478             5 => 2,
1479             6 => 1,
1480             );
1481              
1482             return exists $map{$level} ? $map{$level} : 6;
1483             }
1484              
1485             sub _sizeStringToNumber {
1486             my ($cw,$string) = @_;
1487              
1488             my %map = (
1489             'xx-large' => 6,
1490             'x-large' => 5,
1491             'large' => 4,
1492             'medium' => 3,
1493             'small' => 2,
1494             'x-small' => 1,
1495             'xx-small' => 0,
1496             );
1497              
1498             return exists $map{$string} ? $map{$string} : 3;
1499             }
1500              
1501             sub _isNumber {
1502             my ($cw,$number,$default) = @_;
1503              
1504             if (defined $number && length $number && $number !~ /[^0-9]/) {
1505             return $number;
1506             }
1507             else {
1508             return $default;
1509             }
1510             }
1511              
1512             sub _getOLsym {
1513             my ($cw,$type,$pos) = @_;
1514              
1515             my %letterhash = (
1516             0 => '',
1517             1 => 'A',
1518             2 => 'B',
1519             3 => 'C',
1520             4 => 'D',
1521             5 => 'E',
1522             6 => 'F',
1523             7 => 'G',
1524             8 => 'H',
1525             9 => 'I',
1526             10 => 'J',
1527             11 => 'K',
1528             12 => 'L',
1529             13 => 'M',
1530             14 => 'N',
1531             15 => 'O',
1532             16 => 'P',
1533             17 => 'Q',
1534             18 => 'R',
1535             19 => 'S',
1536             20 => 'T',
1537             21 => 'U',
1538             22 => 'V',
1539             23 => 'W',
1540             24 => 'X',
1541             25 => 'Y',
1542             26 => 'Z',
1543             );
1544              
1545             if ($type =~ /^[0-9]+$/) {
1546             # Numeric types are easy.
1547             return $pos;
1548             }
1549             elsif ($type eq 'I') {
1550             # Roman numerals.
1551             return uc ($cw->_roman($pos));
1552             }
1553             elsif ($type eq 'i') {
1554             # Roman numerals.
1555             return lc ($cw->_roman($pos));
1556             }
1557             elsif ($type =~ /^[A-Za-z]+$/) {
1558             # Alphabetic.
1559             my $string = '';
1560             while ($pos > 26) {
1561             my $first = $pos % 26;
1562             my $second = ($pos - $first) / 26;
1563             $string = $letterhash{$first} . $string;
1564             $pos = $second;
1565             }
1566              
1567             $string = $letterhash{$pos} . $string;
1568              
1569             if ($type =~ /^[A-Z]+$/) {
1570             return uc($string);
1571             }
1572             else {
1573             return lc($string);
1574             }
1575             }
1576              
1577             return $pos;
1578             }
1579              
1580             sub _getULsym {
1581             my ($cw,$type) = @_;
1582              
1583             my $circle = chr(0x25cb);
1584             my $disc = chr(0x25cf);
1585             my $square = chr(0x25aa);
1586              
1587             if ($type =~ /^circle$/i) {
1588             return $circle;
1589             }
1590             elsif ($type =~ /^square$/i) {
1591             return $square;
1592             }
1593             else {
1594             return $disc;
1595             }
1596             }
1597              
1598             sub _roman {
1599             my ($cw,$dec) = @_;
1600              
1601             0 < $dec and $dec < 4000 or return undef;
1602              
1603             my %roman2arabic = qw(I 1 V 5 X 10 L 50 C 100 D 500 M 1000);
1604             my %roman_digit = qw(1 IV 10 XL 100 CD 1000 MMMMMM);
1605             my @figure = reverse sort keys %roman_digit;
1606             $roman_digit{$_} = [ split(//, $roman_digit{$_}, 2) ] foreach @figure;
1607              
1608             my ($x,$roman);
1609             foreach (@figure) {
1610             my ($digit, $i, $v) = (int($dec / $_), @{$roman_digit{$_}});
1611             if (1 <= $digit and $digit <= 3) {
1612             $roman .= $i x $digit;
1613             }
1614             elsif ($digit == 4) {
1615             $roman .= join("", $i, $v);
1616             }
1617             elsif ($digit == 5) {
1618             $roman .= $v;
1619             }
1620             elsif (6 <= $digit and $digit <= 8) {
1621             $roman .= $v . ($i x ($digit - 5));
1622             }
1623             elsif ($digit == 9) {
1624             $roman .= join("", $i, $x);
1625             }
1626             $dec -= $digit * $_;
1627             $x = $i;
1628             }
1629              
1630             return $roman;
1631             }
1632              
1633             sub _blockedTag {
1634             my ($self,$tag) = @_;
1635              
1636             my $deny = 0;
1637              
1638             # If we have defined any "allowed tags", check it.
1639             if (scalar keys %{$self->{hypertext}->{allow}} > 0) {
1640             $deny = 1;
1641              
1642             # See if this tag is allowed.
1643             if (exists $self->{hypertext}->{allow}->{$tag}) {
1644             $deny = 0;
1645             }
1646             }
1647              
1648             # If we have any "denied tags", check them.
1649             if (scalar keys %{$self->{hypertext}->{deny}} > 0) {
1650             if (exists $self->{hypertext}->{deny}->{$tag}) {
1651             $deny = 1;
1652             }
1653             }
1654              
1655             return $deny;
1656             }
1657              
1658             sub _brokenImage {
1659             return q~iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAAK/INwWK6QAAABl0RVh0
1660             U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAKTSURBVHjaYmxpafnPMEAgPT2dASCAWECM
1661             6upqxoFwwJs3b/4DBBATwwADgAAacAcABNCAOwAggAbcAQABNOAOAAigAXcAQAANuAMAAmjAHQAQ
1662             QAPuAIAAItoBjIyM04D4PxKeBhVPBuIzyOJAzEesuQABxESk5S4uoqKZ/6urGf63tjL89/ZmUGJh
1663             yQSKBwGlM8/U1hr/Ly5m+C8oyJAK5DNAMFEAIIAYQJUREDAQwkAwrYON7f9/Tc3//5WV/+8WEAAJ
1664             7i53c/v/v7T0/xsGhv8xQAwUOwPEfMSY+fr16/8AAUSKA+SB+O4hoCXAcP7/X1b2f7mu7v//RUX/
1665             /7Ow/I8GigtCHJBMjHkwBwAEENFpAKjhIZCaXosQYOjg5GRgWLWKYfKfPwwngELvgfJAdXNJSYQA
1666             AURSLgAa3nOQgWH1ahkZBgY2NgaGW7cYnj95wvAaFDRg+xk6Sc0FAAFEkgOAiQ4UDcbGJiYMDOzs
1667             DD8+fGBgBgrYAbE4AwMwBhhcSHUAQACRWg6Ud7i4KClxcTE8uX6dYRdQ4C0Q6wPxVIh8JilZEAQA
1668             AoiUciBZSUgos9zIiIFh+XKGFqBYPhCD4p4ViJ2BOA0YOkCqgxQHAAQQseUAyFeZq8zNGRjmzmUo
1669             +vePYQ9Q4AEw0YF8fhaILwOxJxDzQkIhiFgHAAQQC5HqMssZGY0/b9/OEAvk3IEkunugRAe0nBno
1670             iDRQWvgAxI5AvAlSEK0jxmCAACKqHAAVLKCCiAGSz/9D7GcogcoZAPFMJLndQBxEbDkAEEAsRGa/
1671             T0AqC4rR5S6AWthQTDIACKABrw0BAmjAHQAQQAPuAIAAGnAHAATQgDsAIIAG3AEAATTgDgAIIEZw
1672             q2QAAUCAAQBj+lYRrQ+vagAAAABJRU5ErkJggg==~;
1673             }
1674              
1675             1;
1676              
1677             __END__