package RISCOS::Units;

require Exporter;
use Carp;
use strict;
use vars qw (@ISA @EXPORT_OK $VERSION $AUTOLOAD %factors);

@ISA = qw(Exporter);
@EXPORT_OK = qw(pack_transform unpack_transform
		pack_transform_block unpack_transform_block);
$VERSION = 0.03;
# 0.02 does Map
# 0.03 packs transform blocks

%factors = (
  draw => 1, os => 256, inch => 256 * 180, user => 1,
  point => 256 * 180 / 72, millipoint => 256 / 400);

my ($a, $b);
foreach $a (keys %factors) {
    foreach $b (keys %factors) {
	push @EXPORT_OK, "${a}2$b" unless $a eq $b;
	# Dynamic function generation :-)
    }
}

sub pack_transform {
    return pack 'i', $_[0] * 65536 unless wantarray;
    map  { pack 'i', $_ * 65536 } @_;
}

sub unpack_transform {
    return unpack ('i', $_[0]) / 65536 unless wantarray;
    map  { unpack ('i', $_) / 65536 } @_;
}

sub pack_transform_block {
    my $input = ref $_[0] ? $_[0] : \@_;
    join ('', pack_transform ((@$input)[0..3])) . pack 'i2', (@$input)[4,5]
}

sub unpack_transform_block {
    my $result = [];
    $_[0] =~ /^(....)(....)(....)(....)(.{8})/s;
    @$result = (unpack_transform ($1, $2, $3, $4), unpack 'i2', $5);
    wantarray ? @$result : $result;
}

sub AUTOLOAD {
    my($from, $to);
    ($from, $to) = $AUTOLOAD =~ /.*::(\S+)2(\S+)/;
    $from = $factors{$from};
    $to = $factors{$to};
     
    croak "Undefined subroutine $AUTOLOAD"
      unless defined ($from) && defined ($to);
      
    return $_[0] / $to * $from unless wantarray;
    map  { $_ / $to * $from } @_;
}
1;

__END__

=head1 NAME

RISCOS::Units -- conversions between various S<RISC OS> screen measurement units

=head1 SYNOPSIS

    use RISCOS::Units qw(draw2inch);
    $length = draw2inch ($x2 - $x1);

=head1 DESCRIPTION

This module provides conversion functions between a variety of different
S<RISC OS> measures of screen length. Currently known units are

=over 4

=item draw 

draw units, as used by the C<Draw> module. 256 per OS unit, 46080 per inch.

=item inch

as used in the real world.

=item os

os units, as used by the VDU drivers. Nominally there are 180 in an inch.

=item point

points, 1/72 of an inch

=item millipoint

1/1000th of a point, as used by the Font manager

=item user

User draw units. Currently the exchange rate is fixed at 1:1 with draw units.

=back

All conversion functions are actually created dynamically by the exporter and
the C<AUTOLOAD>er, but this implementation is transparent. Just C<use> them and
call them as if they were declared in full in C<EXPORT_OK> (OK, the one caveat
is that you can't pass in pattern match variables such as C<$1>).

To convert from OS units to draw units just call

    $as_draw = os2draw ($as_os)

To convert a list call in list context

    ($length, $width) = draw2point @size;

=head1 DRAW TRANSFORM MATRICES

Four other functions are provided to convert the values draw transform matrices
to numbers

=over 4

=item pack_transform <scale>, ...

takes a list of numbers, and converts each to a fixed point 32 bit number packed
in a 4 byte scalar for use in a draw transform matrix. Returns a list of packed
scalars in list context, in scalar context returns just the first scalar.

=item unpack_transform <packed_scale>, ...

takes a list of fixed point 32 bit numbers packed into separate scalars, and
converts each to a number. Returns a list of numbers in list context, in scalar
context returns just the first number.

=item pack_transform_block <transform_array>

packs an array of 6 values into a Draw transform block. If the first argument is
an array reference it is taken as pointing to the array to use, else the
argument array is packed. 

=item unpack_transform_block <transform_block>

unpacks a Draw transform block into an array of 6 values. In scalar context
returns a reference to the array, in array context the array itself.

=back

=head1 BUGS

Not tested enough.

=head1 AUTHOR

Nicholas Clark <F<nick@unfortu.net>>

=cut
