/usr/local/CPAN/PPresenter/PPresenter/Formatter/Markup_placer.pm
# Copyright (C) 2000-2002, Free Software Foundation FSF.
package PPresenter::Formatter::Markup_placer;
@EXPORT = qw(place);
use strict;
use Exporter;
use base 'Exporter';
sub flush_line();
sub process_list($;);
my $tagnr = 0;
sub new_tag() { "t".$tagnr++; }
#
# PLACE
# Run through the parsed tree, and assign locations... This work has to
# be redone when the fontsize changes.
#
my (%b, %g);
my ($self, $viewport);
sub place($$$)
{ my ($my_self, $show, $slide, $view, $part, $dx, $parsed) = @_;
$self = $my_self;
my $canvas = $view->canvas;
my $now = new_tag;
$view->addProgram('', $now);
%b = # Variables which change in blocks.
( fonttype => 'PROPORTIONAL'
, fontsize => $view->findFontSize(undef, 'normal')
, fontweight => 'normal'
, fontslant => 'roman'
, underline => 0
, overstrike => 0
, backdrop => $view->hasBackdrop
, color => $view->color('FGCOLOR')
, indent => 0
, rindent => 0 # right-side indentation.
, reformatText=> 1
, align => 'left'
, basealign => 'none'
, is_paragraph => 0
, nesting => 0
, dynamictag => $now
, listitem => undef
);
%g = # Variables which are not block related.
( canvas => $view->canvas
, slide => $slide
, view => $view
, viewport => $view->viewport
, formatter => $self
# current line
, lineascent => 0
, linedescent=> 0
, needs_space=> 0
, accumx => 0
, accumy => $part->{y}
, makeLI => undef
, left_image_end => 0
, right_image_end => 0
, image_indent => 0
, image_rindent => 0
);
map {$g{$_} = $part->{$_}} qw/x y w h slidetag parttag/;
$g{x} += $dx;
no_marks();
process_list($parsed);
flush_line;
}
#
# Handling the commands
#
my %command_action;
%command_action =
( A => sub {}
, B => sub { $b{fontweight} = 'bold' }
, BD => sub { $b{backdrop} = 1 }
, BQ => sub { start_paragraph(@_);
$b{indent} += 0.1*$g{w};
$b{rindent} += 0.1*$g{w}; }
, BR => sub { start_paragraph(@_) }
, 'CENTER' => sub { start_paragraph(@_); $b{align} = 'center' }
, DIV => sub { start_paragraph(@_) }
, I => sub { $b{fontslant} = 'italic' }
, IMG => \&include_image
, LI => sub { start_paragraph(@_); change_font(@_);
$g{makeLI} = $b{dynamictag} }
, MARK => \&set_mark
, N => sub { $b{fontweight} = 'normal' }
, O => sub { $b{overstrike} = 1 }
, OL => \&ordered_list
, P => sub { flush_line; skip_line(@_); start_paragraph(@_) }
, PRE => sub { $b{fonttype} = 'FIXED'; $b{reformatText} = 0;
start_paragraph(@_) }
, PROP => sub { $b{fonttype} = 'PROPORTIONAL' }
, REDO => \&restore_mark
, RIGHT => sub { start_paragraph(@_); $b{align} = 'right' }
, SUB => sub { $b{fontsize} -=2; $b{basealign} = 'bottom' }
, SUP => sub { $b{fontsize} -=2; $b{basealign} = 'top' }
, TEXT => sub {}
, TT => sub { $b{fonttype} = 'FIXED' }
, U => sub { $b{underline} = 1 }
, UL => \&unordered_list
);
sub ordered_list
{ my $params = shift;
start_paragraph(@_);
my ($size, $perc, $src) = $self->nestInfo($g{view}, $b{nesting}++);
$b{indent} = $self->takePercentage($perc,$g{w});
$params->{SIZE} = $size unless exists $params->{SIZE};
$b{listitem} = exists $params->{START} ? delete $params->{START} : 1;
}
sub unordered_list
{ my $params = shift;
start_paragraph(@_);
(my $size, my $perc, $b{listitem})
= $self->nestInfo($g{view}, $b{nesting}++);
$b{indent} = $self->takePercentage($perc,$g{w});
$params->{SIZE} = $size unless exists $params->{SIZE};
$b{listitem} = $g{view}->image(-file => delete $params->{SRC})
if exists $params->{SRC};
$b{listitem}->prepare(@g{ qw/viewport canvas/ } )
if defined $b{listitem};
}
sub include_image($)
{ my $params = shift;
my $src = exists $params->{SRC} ? delete $params->{SRC} : 'dot_green.gif';
my $align = exists $params->{ALIGN} ? lc delete $params->{ALIGN} : 'center';
my $img = $g{view}->image
( -file => $src
, (exists $params->{RESIZE} ? (-resize => delete $params->{RESIZE} ) : ())
, (exists $params->{ENLARGE}? (-enlarge => delete $params->{ENLARGE}) : ())
, (exists $params->{BASE} ? (-sizeBase=> delete $params->{BASE}) : ())
);
return unless defined $img;
$img->prepare(@g{ qw/viewport canvas/ });
my ($width, $height) = $img->dimensions($g{viewport});
my $hspace = $self->takePercentage(
(exists $params->{HSPACE} ? delete $params->{HSPACE}
: $self->{-imageHSpace}), $width);
my $vspace = $self->takePercentage(
(exists $params->{VSPACE} ? delete $params->{VSPACE}
: $self->{-imageVSpace}), $height);
my ($imgx, $imgy);
flush_line;
if($align eq 'left')
{ $imgx = $b{indent};
$imgy = $g{accumy};
$imgy += $vspace unless $g{accumy} == $g{'y'};
$g{left_image_end} = $imgy + $height + $vspace;
$g{image_indent} = $b{indent} +$width +$hspace;
}
elsif($align eq 'right')
{ $imgx = $g{w} -$b{rindent}-$width;
$imgy = $g{accumy};
$imgy += $vspace unless $g{accumy} == $g{'y'};
$g{right_image_end} = $imgy + $height + $vspace;
$g{image_rindent} = $b{rindent} +$width +$hspace;
}
elsif($align eq 'center')
{ $imgx = ($g{w} / 2) - ($width / 2);
$imgy = $g{accumy};
$imgy += $vspace unless $g{accumy} == $g{'y'};
$g{accumy} += $height;
}
else
{ warn "Unknown image alignment key '$align'.\n";
return
}
$img->show(@g{ qw/viewport canvas/ },
, $g{'x'} + $imgx, $imgy
, -tags => [ $g{slidetag}, $g{parttag}, $b{dynamictag} ]
, -anchor => 'nw'
);
}
sub change_font($)
{ my $params = shift;
if(exists $params->{LARGE})
{ delete $params->{LARGE};
$params->{SIZE} = '+1';
}
if(exists $params->{SMALL})
{ delete $params->{SMALL};
$params->{SIZE} = '-1';
}
$b{fontsize} = $g{view}->findFontSize($b{fontsize}, delete $params->{SIZE})
if exists $params->{SIZE};
$b{fonttype} = delete $params->{FACE} if exists $params->{FACE};
if(exists $params->{TT}){$b{fonttype}='FIXED'; delete $params->{TT}}
if(exists $params->{PROP}){$b{fonttype}='PROPORTIONAL'; delete $params->{PROP}}
if(exists $params->{B}) {$b{fontweight}='bold'; delete $params->{B}}
if(exists $params->{I}) {$b{fontweight}='italic'; delete $params->{I}}
if(exists $params->{N}) {$b{fontweight}='normal'; delete $params->{N}}
if(exists $params->{BD}){$b{backdrop}=delete $params->{BD} }
$b{color} = $g{view}->color(delete $params->{COLOR})
if exists $params->{COLOR};
if(exists $params->{SHOW})
{ $b{dynamictag} = new_tag;
my $when = delete $params->{SHOW};
$when =~ s/"//g;
$g{view}->addProgram($when, $b{dynamictag});
}
}
sub start_paragraph()
{ flush_line;
my $params = shift;
if(exists $params->{CLEAR})
{ my $dir = uc delete $params->{CLEAR};
if($dir ne 'LEFT' && $dir ne 'RIGHT' && $dir ne 'ALL')
{ warn "WARNING slide \"$g{slide}\": CLEAR left|right|all, not $dir.\n";
$dir = 'ALL';
}
$g{accumy} = $g{left_image_end}
if ($dir eq 'LEFT' || $dir eq 'ALL')
&& $g{accumy} < $g{left_image_end};
$g{accumy} = $g{right_image_end}
if ($dir eq 'RIGHT' || $dir eq 'ALL')
&& $g{accumy} < $g{right_image_end};
}
$b{align} = delete $params->{ALIGN} if exists $params->{ALIGN};
$b{is_paragraph} = 1;
}
sub skip_line(;)
{ my $font = create_font();
my %metrics = $g{canvas}->fontMetrics($font);
$g{accumy} += $self->takePercentage($self->{-lineSkip},
$metrics{-ascent} + $metrics{-descent});
}
#
# Markings.
#
my %marks;
sub no_marks()
{ %marks = ();
}
sub set_mark($)
{ my $params = shift;
my $name = exists $params->{NAME}
? delete $params->{NAME}
: 'default';
if(exists $marks{$name})
{ warn "Mark named $name already defined.";
return;
}
my %b_copy = %b;
my %g_copy = %g;
$marks{$name} = [ \%b_copy, \%g_copy ];
}
sub restore_mark($;)
{ my $params = shift;
my $name = exists $params->{NAME}
? delete $params->{NAME}
: 'default';
unless(exists $marks{$name})
{ warn "No mark with named $name. Skipped.";
return;
}
flush_line;
%b = %{$marks{$name}->[0]};
%g = %{$marks{$name}->[1]};
}
#
# Handling text.
#
sub create_font()
{ $g{viewport}->font(@b{qw/fonttype fontweight fontslant fontsize/} );
}
sub current_bounds()
{
( $g{left_image_end} <= $g{accumy}
? $b{indent} : $g{image_indent}
, $g{right_image_end} <= $g{accumy}
? $b{rindent} : $g{image_rindent})
}
sub process_line($)
{ my ($line) = @_;
return unless defined $line;
# Fonts change all the time :(
my $font = create_font;
# Also the width changes constantly.
my ($left, $right) = current_bounds;
my $maxwidth = $g{w} - $left - $right;
if($b{reformatText})
{ # Layout multiline.
foreach (split /(\s+)/s,$line,-1)
{
next if $_ eq '';
if(/\s+/s)
{ $g{needs_space} = (exists $g{linewords} && @{$g{linewords}} > 0);
next; # will be space at the end or begin string.
}
my $word = decode_string($_);
my $put = $g{needs_space} ? " $word" : $word;
my $wordlength = $g{canvas}->fontMeasure($font, $put);
$g{needs_space} = 0;
if( $g{accumx}+$wordlength > $maxwidth
&& $g{accumx}!=0 ) # Long words will have their own line..
{ flush_line;
($left, $right) = current_bounds;
$maxwidth = $g{w} - $left - $right;
redo; # Try same word again.
}
my %metrics = $g{canvas}->fontMetrics($font);
my ($ascent,$descent) = @metrics{'-ascent', '-descent'};
$g{lineascent} = $ascent if $g{lineascent} < $ascent;
$g{linedescent}= $descent if $g{linedescent}<$descent;
push @{$g{linewords}}, [ $put, $g{accumx}, $ascent, $font,
$b{color}, $b{dynamictag}, $b{backdrop}, $b{basealign} ];
$g{accumx} += $wordlength;
}
}
else
{ # Preformatted text.
my @lines = split /\n/, $line, -1;
while(@lines>0)
{ my $put = decode_string(shift @lines);
$put = " ".$put if $g{needs_space};
my $wordlength = $g{canvas}->fontMeasure($font, $put);
my %metrics = $g{canvas}->fontMetrics($font);
my ($ascent,$descent) = @metrics{'-ascent', '-descent'};
$g{lineascent} = $ascent if $g{lineascent} < $ascent;
$g{linedescent}= $descent if $g{linedescent}<$descent;
push @{$g{linewords}},
[ $put, $g{accumx}, $ascent, $font, $b{color}
, $b{dynamictag}, $b{backdrop}, $b{basealign} ];
$g{accumx} += $wordlength;
flush_line if @lines > 0;
}
$g{needs_space} = 0;
}
}
sub flush_line()
{ $g{needs_space} = 0;
return unless exists $g{linewords};
my $slide = $g{slide};
# Alignment
my ($left, $right) = current_bounds;
my $xcorrect = $g{'x'}
+ ( $b{align} eq 'left' ? $left
: $b{align} eq 'right' ? $g{w} - $right - $g{accumx}
: $left + ($g{w} -$left -$right -$g{accumx})/2
);
$g{lineascent} *= (1.0 + $self->toPercentage($self->{-listSkip}))
if $g{makeLI};
my $baseline = $g{accumy} + $g{lineascent};
$g{accumy} += $g{lineascent} + $g{linedescent};
# Realization of words.
my $refascent = 0;
my $baseoffset;
my @linewords = @{$g{linewords}};
while(@linewords>0)
{ my $word = shift @linewords; # [ text,x,ascent,font,color,tag,bd,va ]
my ($text, $x, $ascent, $font, $color, $dynamictag, $backdrop,
$vert_align) = @$word;
while(@linewords > 0
&& $linewords[0][2]==$ascent && $linewords[0][3]."" eq $font.""
&& $linewords[0][4] eq $color && $linewords[0][5] eq $dynamictag
&& $linewords[0][6] eq $backdrop && $linewords[0][7] eq $vert_align)
{ # Note: same font but different ascent possible for sub/superscript
$text .= $linewords[0][0];
shift @linewords;
}
my @tags = @g{ 'slidetag', 'parttag' };
push @tags, $dynamictag if defined $dynamictag;
$baseoffset = $vert_align eq 'top' ? int($refascent*1.1-$ascent)
: $vert_align eq 'bottom' ? int(-$refascent*.2)
: 0;
if($backdrop)
{ my $backdrop = int ($ascent/12);
$g{canvas}->createText
( $x+$xcorrect+$backdrop,
, $baseline-$ascent+$backdrop-$baseoffset
, -text => $text
, -anchor => 'nw'
, -fill => $g{view}->color('BDCOLOR')
, -font => $font
, -tags => \@tags
, -width => 0
);
}
$g{canvas}->createText
( $x+$xcorrect, $baseline-$ascent-$baseoffset
, -text => $text
, -anchor => 'nw'
, -fill => $color
, -font => $font
, -tags => \@tags
, -width => 0
);
$refascent = $ascent;
}
# List-items can only be produced when we know the height of
# the line. That's now!
if(defined $g{makeLI} && defined $b{listitem})
{
if(ref $b{listitem})
{ # LI as part of UL
$b{listitem}->show( @g{ qw/viewport canvas/ }
, $xcorrect-10, int ($baseline - $g{lineascent}/3)
, -tags => [ @g{ qw/slidetag parttag makeLI/ } ]
, -anchor => 'e'
);
}
else
{ # LI as part of OL
my $number = $b{listitem}++ . '.';
$g{canvas}->createText
( $xcorrect-5, $baseline + $g{linedescent}
, -text => $number
, -anchor => 'se'
, -fill => $g{linewords}[0][4] # forms to font and color
, -font => $g{linewords}[0][3] # of first word in line.
, -tags => [ @g{ qw/slidetag parttag makeLI/ } ]
, -width => 0
);
}
}
# Reset line.
delete $g{linewords};
$g{lineascent} = 0;
$g{linedescent} = 0;
$g{accumx} = 0;
$g{makeLI} = undef;
}
sub process_list($;)
{ my $parsed = shift;
my %safe_b = %b;
$b{is_paragraph} = 0;
for(my $str=0; $str< @$parsed; $str+=2)
{
if(ref $parsed->[$str] eq 'ARRAY')
{ process_list($parsed->[$str]);
process_line($parsed->[$str+1]);
next;
}
delete $parsed->[$str]{cmd}; # cmd as the user specified.
my $cmd = delete $parsed->[$str]{CMD}; # translated cmd.
if(not exists $command_action{$cmd})
{ warn "Unknown command $cmd used.\n"; }
elsif(not defined $command_action{$cmd})
{ warn "Command $cmd not yet implemented.\n"}
else
{ &{$command_action{$cmd}}($parsed->[$str]);
change_font($parsed->[$str]);
foreach my $k (keys %{$parsed->[$str]})
{ warn "Slide $g{slide}, unknown parameter $k for $cmd.\n";
}
}
process_line($parsed->[$str+1])
if defined $parsed->[$str+1]; # only undef at toplevel
}
flush_line if $b{is_paragraph};
%b = %safe_b;
}
sub decode_string($)
{ my $string = shift;
return '' if !defined $string || $string eq '';
@_ = split /\&(\w+)\;/, $string;
return $string if @_ == 1; # no specials
my $translations = $self->{specials};
my $decoded = shift;
while(@_)
{ my $char = shift;
if(exists $translations->{$char})
{ $decoded .= $translations->{$char};
}
else
{ warn "Cannot decode &$char; in $string" if $^W;
$decoded .= "&$char;";
}
$decoded .= shift if @_;
}
$decoded;
}
1;