# -*- perl -*-

#
# $Id: RotFont.pm,v 1.15 2005/11/19 00:11:15 eserte Exp $
# Author: Slaven Rezic
#
# Copyright (C) 2000,2001 Slaven Rezic. All rights reserved.
# This package is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: eserte@cs.tu-berlin.de
# WWW:  http://user.cs.tu-berlin.de/~eserte/
#

package Tk::RotFont;

use strict;
use vars qw($DEBUG);
use constant PI => 3.141592653;

BEGIN {
    if (!$Tk::RotFont::NO_X11) {
	eval q{
	    use Tk::X11Font;
	    use Tk::Font;
	}; die $@ if $@;
    }
}


#*canvas = \&canvas_old;
*canvas = \&rot_text_old;
#*canvas = \&rot_text_smart_compat;
#*rot_text = \&rot_text_old;

if (1) {
    # irgendwas ist hier kaputt gegangen... $Tk::VERSION >= 804
    # nö, XFree86 4 ist der Schuldige!
    $Tk::RotFont::NO_X11 = 1;
}

# XXX durch Variable verfügbar machen
if (!$Tk::RotFont::NO_X11) { # XXX rot_text_newer ist wesentlich *langsamer* als rot_text_old
    # X11::Protocol scheint Speicherfresser zu sein
    use vars qw($use_rotx11font); # XXX
    if (!defined $main::x11) {
	eval '
	    require X11::Protocol;
	    $main::x11 = X11::Protocol->new;
	    #use lib "XXX$ENV{HOME}/devel";
	    require Tk::RotX11Font;
	    if ($main::use_font_rot) {
		$use_rotx11font = 1;
#		*rot_text = \&rot_text_newer;
		*canvas = \&rot_text_smart_compat;
	    }
	';
	warn $@ if $@;
    }
}

use vars qw(%rot_font_cache);

# Zeichnet den Straßennamen mit rotierten Zeichensätzen.
# Argumente:
# $c:        Canvas
# $abk:      Abkürzung, wird als Tag verwendet
# $coordref: Referenz auf Koordinaten der Straße
# $f_sub:    Funktion, die den Fontnamen ermittelt
# $size:     Fontgröße
# $str:      auszugebendes Label
# %args:     more arguments for createText
### AutoLoad Sub
sub rot_text_old {
    my($c, $abk, $coordref, $f_sub, $size, $str, %args) = @_;
    return if length($str) == 0;
    my $ges_strecke_len = 0;
    my $last_coordref = $#{$coordref};
    for(my $i = 0; $i<=$last_coordref-3; $i+=2) {
	$ges_strecke_len +=
	  Strassen::Util::strecke([$coordref->[$i],   $coordref->[$i+1]],
				  [$coordref->[$i+2], $coordref->[$i+3]]);
    }
    return if $ges_strecke_len == 0;
    my $len_per_char = (length($str) == 1 
			? 0 : $ges_strecke_len/(length($str)+1));
    return if $len_per_char < 4; # ansonsten unlesbar
    my $reversed = 0;
    if ($coordref->[0] > $coordref->[$last_coordref-1]) {
	$str = reverse $str;
	$reversed = 1;
    }
    my $last_strecke_len;
    my $strecke_len = 0;
    my $curr_pos = $len_per_char;
    my $curr_i = 0;
    my @create_text_args = (-anchor => 'w', -tags => "$abk-label", %args);
    for(my $i = 0; $i<=$last_coordref-3; $i+=2) {
	$last_strecke_len = $strecke_len;
	$strecke_len +=
	  Strassen::Util::strecke([$coordref->[$i],   $coordref->[$i+1]],
				  [$coordref->[$i+2], $coordref->[$i+3]]);
	while ($strecke_len > $curr_pos) {
	    my($ch_x, $ch_y);
	    my $m = ($curr_pos-$last_strecke_len)/
	      ($strecke_len-$last_strecke_len);
	    $ch_x = $m*($coordref->[$i+2]-$coordref->[$i])
	      + $coordref->[$i];
	    $ch_y = $m*($coordref->[$i+3]-$coordref->[$i+1])
	      + $coordref->[$i+1];
	    my $rotsize;
	    if ($main::use_font_rot) {
		my $r = -atan2($coordref->[$i+3]-$coordref->[$i+1],
			       $coordref->[$i+2]-$coordref->[$i],
			      );
		if ($reversed) {
		    $r += PI;
		}
		$rotsize = get_rot_matrix($r, $size);
	    } else {
		$rotsize = $size*10;
	    }
	    eval {
		my $substr = substr($str, $curr_i, 1); # workaround Tk804 problem
		$c->createText
		    ($ch_x, $ch_y,
		     -text => $substr,
		     -font => $f_sub->($rotsize),
		     @create_text_args,
		    );
	    };
	    if ($@) { warn "Problem at $rotsize: $@\n" }
	    $curr_i++;
	    $curr_pos += $len_per_char;
	}
    }
}

# Zeichnet den Straßennamen mit rotierten Zeichensätzen.
# Verwendet Tk::RotX11Font.
# XXX Test2
### AutoLoad Sub
sub rot_text_newer {
    my($c, $abk, $coordref, $f_sub, $size, $str) = @_;
    return if length($str) == 0;
    my $ges_strecke_len = 0;
    my $last_coordref = $#{$coordref};
    for(my $i = 0; $i<=$last_coordref-3; $i+=2) {
	$ges_strecke_len +=
	  Strassen::Util::strecke([$coordref->[$i],   $coordref->[$i+1]],
				  [$coordref->[$i+2], $coordref->[$i+3]]);
    }
    return if $ges_strecke_len == 0;
    my $len_per_char = (length($str) == 1 
			? 0 : $ges_strecke_len/(length($str)+1));
    return if $len_per_char < 4; # ansonsten unlesbar

    if ($coordref->[0] > $coordref->[$#$coordref-1]) {
	my(@newcoordref);
	for(my $i=0; $i<$#$coordref; $i+=2) {
	    unshift @newcoordref, $coordref->[$i], $coordref->[$i+1];
	}
	$coordref = \@newcoordref;
    }

    my $last_strecke_len;
    my $strecke_len = 0;
    my $curr_pos = $len_per_char;
    my $str_i = 0;
    eval {
      STRLOOP:
	for(my $i = 0; $i<=$last_coordref-3; $i+=2) {
	    $last_strecke_len = $strecke_len;
	    $strecke_len +=
	      Strassen::Util::strecke([$coordref->[$i],   $coordref->[$i+1]],
				      [$coordref->[$i+2], $coordref->[$i+3]]);
	    my $r = -atan2($coordref->[$i+3]-$coordref->[$i+1],
			   $coordref->[$i+2]-$coordref->[$i],
			  );
	    my $rotfont1 = new Tk::RotX11Font
	      substr($str, $str_i), $f_sub, $size, $r;
	    while ($strecke_len > $curr_pos) {
		last STRLOOP if ($str_i > length($str));
		my($ch_x, $ch_y);
		my $m = ($curr_pos-$last_strecke_len)/
		  ($strecke_len-$last_strecke_len);
		$ch_x = $m*($coordref->[$i+2]-$coordref->[$i])
		  + $coordref->[$i];
		$ch_y = $m*($coordref->[$i+3]-$coordref->[$i+1])
		  + $coordref->[$i+1];
		my $ch = substr($str, $str_i, 1);
		my($xext1, $yext1) = $rotfont1->x_y_extent($ch);
		$rotfont1->writeCanvas($c, $ch_x, $ch_y, "$abk-label", $ch);
		$str_i++;
		$curr_pos += CORE::sqrt(sqr($xext1) + sqr($yext1));
	    }
	}
    };
    warn $@ if $@;
}

# Kompatibilitätsaufruf
sub rot_text_smart_compat {
    my($c, $abk, $coordref, $f_sub, $size, $str) = @_;
    rot_text_smart($str, $coordref,
		   -anglesteps => 1,
		   -fontsub => $f_sub,
		   -size => $size,
		   -canvas => $c,
		   -abbrev => $abk);
}

# Zeichnet den Straßennamen mit rotierten Zeichensätzen.
# Der Straßenname wird dabei in zwei Teile geteilt und am
# Anfang und Ende der Straße gezeichnet.
# Verwendet entweder Tk::RotX11Font oder benutzerdefinierte Funktionen
### AutoLoad Sub
sub rot_text_smart {
    my($str, $coordref, %args) = @_;
    return if length($str) == 0;
    # Aufteilen in "Duden-" und "str." (wenn möglich)
    # XXX mehrteilige Straßennamen müssen nicht geteilt werden
    # (Hallesches|Ufer, Kaiser-|Wilhelm-|Platz)
    my($strbase, $strtype);
    if ($str =~ /^(.*)(\s+|-)(\S+)$/) {
	($strbase, $strtype) = ($1, $3);
	if ($2 eq '-') { $strbase .= $2 }
    } elsif ($str !~ /^(.*)(str\.    |
			    straße   |
			    damm     |
			    weg      |
			    allee    |
			    chaussee |
			    ring     |
			    platz    |
			    brücke   |
			    ufer)$/ix) {
	return;
    } else {
	($strbase, $strtype) = ($1, $2);
	# Bindestrich bei Bedarf hinzufügen
	if ($strbase =~ /^(.*)(\s+)$/) {
	    $strbase = $1;
	} elsif ($strbase !~ /-$/) {
	    $strbase .= "-";
	}
    }
    $strbase = " $strbase";
    $strtype .= " ";

    if ($coordref->[0] > $coordref->[$#$coordref-1]) {
	my(@newcoordref);
	for(my $i=0; $i<$#$coordref; $i+=2) {
	    unshift @newcoordref, $coordref->[$i], $coordref->[$i+1];
	}
	$coordref = \@newcoordref;
    }

    my @r;
    $r[0] = -atan2($coordref->[0+3]-$coordref->[0+1],
		   $coordref->[0+2]-$coordref->[0],
		  );
    my $coordlen3 = $#$coordref-3;
    $r[1] = -atan2($coordref->[$coordlen3+3]-$coordref->[$coordlen3+1],
		   $coordref->[$coordlen3+2]-$coordref->[$coordlen3],
		  );
    if ($args{-anglesteps}) {
	# 5°-Schritte erzwingen
	foreach (@r) {
	    $_ = int(($_/PI)*36)/36*PI;
	}
    }
    if (ref $args{-drawsub} eq 'CODE') {
return draw_text_exact($str, $coordref, %args);
	# use user defined routine
	# XXX chaos. Die Argumente sind: x, y (nicht transponiert)
	# Straßenname (String), Winkel (rad) (muss - genommen werden?!),
	# optional: delta-w und delta-h (pixel)
	my($w_all) = $args{-extentsub}->($coordref->[0], $coordref->[1],
					 $strbase.$strtype, 0);
	my($w2,$h2) = $args{-extentsub}->($coordref->[-2], $coordref->[-1],
					  $strtype, $r[1]);

	my $ges_strecke_len = len_of_coordrefs($coordref, \%args);
	warn "$strbase wall=$w_all ges=$ges_strecke_len\n" if $DEBUG;
	return if ($ges_strecke_len == 0 || $ges_strecke_len < $w_all);

	warn "r0=" . ($r[0]*180/PI) . " r1=" . ($r[1]*180/PI) . "\n" if $DEBUG;
	$args{-drawsub}->($coordref->[0], $coordref->[1],
			  $strbase, $r[0]);
	$args{-drawsub}->($coordref->[-2], $coordref->[-1],
			  $strtype, $r[1], $w2, $h2);
    } else {
	# use Tk Canvas
	eval {
	    my $f_sub = $args{-fontsub};
	    my $size  = $args{-size};
	    my $c     = $args{-canvas};
	    my $abk   = $args{-abbrev};

	    my $rotfont1 = new Tk::RotX11Font $strbase, $f_sub, $size, $r[0];
	    my $rotfont2 = new Tk::RotX11Font $strtype, $f_sub, $size, $r[1];
	    my($xext1, $yext1) = $rotfont1->x_y_extent;
	    my($xext2, $yext2) = $rotfont2->x_y_extent;
	    if (abs($xext1+$xext2) > abs($coordref->[0]-$coordref->[$#$coordref-1])
		&&
		abs($yext1+$yext2) > abs($coordref->[1]-$coordref->[$#$coordref])
	       ) {
		warn "$strbase $strtype too large..." if $DEBUG;
		return;
	    }
	    $rotfont1->writeCanvas
		($c, $coordref->[0], $coordref->[1], "$abk-label");
	    $rotfont2->writeCanvas
		($c,
		 $coordref->[$#$coordref-1]-$xext2, $coordref->[$#$coordref]-$yext2,
		 "$abk-label");
	};
    }
    warn $@ if $@;
}

sub draw_text_exact {
    my($str, $coordref, %args) = @_;
    my($w_all) = $args{-extentsub}->($coordref->[0], $coordref->[1],
				     $str, 0);
    my $ges_strecke_len = len_of_coordrefs($coordref, \%args);
    my $margin = 5;
    warn "$str wall=$w_all ges=$ges_strecke_len\n" if $DEBUG;
    return if ($ges_strecke_len == 0 || $ges_strecke_len < $w_all+2*$margin);

    warn "coords=@$coordref\n" if $DEBUG;

    my($last_x, $last_y, $section) =
	advance($coordref, \%args, $coordref->[0], $coordref->[1],
		0, $margin);
    warn "advance $margin from $coordref->[0]/$coordref->[1] => $last_x/$last_y\n" if $DEBUG;
    my $last_section = $section;

    my $next_len = 0;
    my $next_i = 2;
    my($next_x, $next_y);
    while($next_i <= $#$coordref) {
	($next_x, $next_y) = ($coordref->[$next_i], $coordref->[$next_i+1]);
	$next_len += Strassen::Util::strecke([$next_x,$next_y],[$last_x,$last_y]);
	last if ($margin < $next_len);
	$next_i+=2;
    };
    my $last_r0 = -atan2($coordref->[$next_i+1]-$last_y,
			 $coordref->[$next_i]-$last_x);
{
my($tx1,$ty1) = $args{-transpose}->($last_x,$last_y);
my($tx2,$ty2) = $args{-transpose}->($coordref->[$next_i],$coordref->[$next_i+1]);
my $tr = -atan2($ty2-$ty1, $tx2-$tx1);
warn "t1=$tx1/$ty1 t2=$tx2/$ty2 tr=$tr\n" if $DEBUG;
}

    my $len_so_far = 0;
    my $r; # next
    my $this_r;

    my $draw = sub {
	my $j = shift;
	my($draw_len) = $args{-extentsub}->($last_x, $last_y,
					    substr($str, 0, $j), 0);
	warn "draw x/y=$last_x/$last_y, str=($str,0,$j), r0=$last_r0 thisr=$this_r\n" if $DEBUG;
	$args{-drawsub}->($last_x, $last_y,
			  substr($str, 0, $j),
			  (defined $r ? in_between($last_r0, $r) : $last_r0)
			 );
	#$last_r0);
	#$this_r);
	$str = substr($str, $j);
	($last_x, $last_y, $section) =
	    advance($coordref, \%args, $last_x, $last_y,
		    $section, $draw_len);
	$last_r0 = $r;
	$len_so_far = $draw_len;
	warn "after ($draw_len): (x/y=$last_x/$last_y, $section) r=$last_r0 len=$len_so_far\n"
	    if $DEBUG;
    };

 LOOP:
    while(1) {
	last if ($section*2+3) > $#$coordref;

	$r = -atan2($coordref->[$section*2+3]-$last_y,
		    $coordref->[$section*2+2]-$last_x);
	$this_r = -atan2($coordref->[$section*2+1]-$last_y,
			 $coordref->[$section*2+0]-$last_x);

	$len_so_far += Strassen::Util::strecke
	    ([$args{-transpose}->($coordref->[$section*2],
				  $coordref->[$section*2+1])],
	     [$args{-transpose}->($coordref->[$section*2+2],
				  $coordref->[$section*2+3])]);

	# zu große Abweichung von der Geraden:
	if (abs($r-$last_r0) > 0.175) {

	    for(my $j = 0; $j < length $str; $j++) {
		if (substr($str, $j, 1) =~ /\s/) {
		    $draw->($j);
		    next LOOP;
		}

		my($w_x) = $args{-extentsub}->($last_x, $last_y,
					       substr($str, 0, $j), 0);
		if ($w_x > $len_so_far) {
		    $draw->($j);
		    next LOOP;
		}
	    }
	}

	$section++;
    }

    if ($str ne "") {
	$draw->(length $str);
    }
}

sub len_of_coordrefs {
    my $coordref = shift;
    my $args = shift;
    my $last_coordref = shift || $#{$coordref};
    my $ges_strecke_len = 0;

    for(my $i = 0; $i<=$last_coordref-3; $i+=2) {
	$ges_strecke_len +=
	    Strassen::Util::strecke
		    ([$args->{-transpose}->($coordref->[$i],
					    $coordref->[$i+1])],
		     [$args->{-transpose}->($coordref->[$i+2],
					    $coordref->[$i+3])]);
    }

    $ges_strecke_len;
}

# Advance on the line represented by the $coordref from point $x/$y by
# $delta and return new $newx,$newy values. The point $x/$y lies on
# section number $section. A new section is also returned. Sections are
# numbered from 0. $args should contain the -transpose subroutine.
sub advance {
    my($coordref, $args, $x, $y, $section, $delta) = @_;
    my $i = $section*2 + 2;
    for(; $i<=$#$coordref; $i+=2) {
	my $this_hop = Strassen::Util::strecke
	    ([$args->{-transpose}->($x, $y)],
	     [$args->{-transpose}->($coordref->[$i],
				    $coordref->[$i+1])]);
	if ($this_hop > 0) {
	    if ($this_hop > $delta) {
		my $scale = $delta/$this_hop;
		return (($coordref->[$i]-$x)*$scale+$x,
			($coordref->[$i+1]-$y)*$scale+$y,
			$i);
	    }
	    $delta -= $this_hop;
	    ($x, $y) = ($coordref->[$i], $coordref->[$i+1]);
	}
    }
    ($x, $y, $#$coordref+1); # $delta is larger than line
}

sub in_between {
    my($a, $b) = @_;
    #warn "a=$a b=$b middle=" . (($a-$b)/2+$a) . "\n";
    ($a-$b)/2+$b;
}

# Erstellt eine Rotationsmatrix für X11R6
# XXX rot-Funktion auslagern (CanvasRotText)
### AutoLoad Sub
sub get_rot_matrix {
    my($r, $size) = @_;
    $r = int(($r/PI)*36)/36*PI; # 5°-Schritte erzwingen
    if (abs($r - PI) < 0.1) {
	$r = 3.2;
    } elsif (abs($r + PI) < 0.1) {
	$r = -3.1;
    }
    my $mat;
    my $a1 = $size*cos($r);
    my $s1 = sin($r);
    foreach ($a1, $size*$s1, $size*-$s1, $a1) {
	s/-/~/g;
	if ($mat) { $mat .= " " }
	$mat .= $_;
    }
    '[' . $mat . ']';
}

# Rotiert den angegebenen Font um $r (Bogenmaß)
### AutoLoad Sub
sub rot_font {
    my($font, $r) = @_;
    my $top = $main::top;
    my $font_obj;
    eval {
	$font_obj = $top->X11Font($font);
    };
    if (!$font_obj) {
	# $font ist ein Tk-font und kann nicht als Argument für
	# Font verwendet werden.
	my(%f) = $top->fontActual($font);
	$font_obj = $top->Font(family => $f{-family},
			       point  => $f{-size}*10,
# Übersetzung zu medium/old etc. nötig
#			       weight => $f{-weight},
			       slant  => 'r',
#XXX Übersetzung zu o/i etc. nötig
#			       slant  => $f{-slant},
			      );
    }
    my $matrix = get_rot_matrix($r, $font_obj->Point/10);
    $font_obj->Point("");
    $font_obj->Pixel($matrix);
    $font_obj->as_string;
}

# Zeichnet den Straßennamen mit rotierten Zeichensätzen.
# XXX kann mit perl nicht zufriedenstellend gelöst werden
### AutoLoad Sub
sub createRotText {
    my($c, $x, $y, %args) = @_;
    my $str  = delete $args{-text};
    my $font = delete $args{-font};
# XXX effizienter gestalten
    my $dummy_l = $c->parent->Label(defined $font ? (-font => $font) : ());
    my $font_n_obj = $dummy_l->cget(-font);

    my $rot  = delete $args{-rot};
    if ($rot) {
	my $cache_name = "$font/$rot";
	if (exists $rot_font_cache{$cache_name}) {
	    $font = $rot_font_cache{$cache_name};
	} else {
	    $font = rot_font($font, $rot);
	    $rot_font_cache{$cache_name} = $font;
	}
    }
    my $anchor = delete $args{-anchor} || 'w';
    foreach (split(//, $str)) {
	$c->createText($x, $y, -text => $_, -font => $font, %args,
		       -anchor => $anchor,
		      );
	my $xadd = $main::top->font('measure', $font_n_obj, $_);
	$y -= $xadd*sin($rot);
	$x += $xadd*cos($rot);
    }
}
# XXX ^^^

1;

__END__
